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:
42
src/ast.zig
42
src/ast.zig
@@ -136,7 +136,7 @@ pub const FnDecl = struct {
|
||||
/// functions, lowering-time objc/protocol method synthesis) leave it zero.
|
||||
name_span: Span = .{ .start = 0, .end = 0 },
|
||||
/// True when the function NAME was written as a backtick raw identifier
|
||||
/// (`` `s2 :: … ``) or synthesized by a `#import c` foreign decl. A raw
|
||||
/// (`` `i2 :: … ``) or synthesized by a `#import c` foreign decl. A raw
|
||||
/// name is exempt from the reserved-type-name binding check.
|
||||
/// Every PARSER fn_decl is built through `parseFnDecl`, whose `name_is_raw`
|
||||
/// is a REQUIRED parameter, so a parser site cannot drop it; the default
|
||||
@@ -165,7 +165,7 @@ pub const Param = struct {
|
||||
/// parameter, lowering substitutes this expression in its place.
|
||||
default_expr: ?*Node = null,
|
||||
/// True when the param name was written as a backtick raw identifier
|
||||
/// (`` `s2 ``) or synthesized by a `#import c` foreign decl. A raw name is
|
||||
/// (`` `i2 ``) or synthesized by a `#import c` foreign decl. A raw name is
|
||||
/// exempt from the reserved-type-name binding check.
|
||||
is_raw: bool = false,
|
||||
};
|
||||
@@ -204,8 +204,8 @@ pub const StringLiteral = struct {
|
||||
|
||||
pub const Identifier = struct {
|
||||
name: []const u8,
|
||||
/// True when written as a backtick raw identifier (`` `s2 ``). Carried so a
|
||||
/// destructure target (`` `s2, b := … ``) can be recognised as raw and
|
||||
/// True when written as a backtick raw identifier (`` `i2 ``). Carried so a
|
||||
/// destructure target (`` `i2, b := … ``) can be recognised as raw and
|
||||
/// exempted from the reserved-type-name binding check.
|
||||
is_raw: bool = false,
|
||||
};
|
||||
@@ -298,7 +298,7 @@ pub const IfExpr = struct {
|
||||
binding_name: ?[]const u8 = null, // for `if val := expr { ... }` optional binding
|
||||
binding_span: ?Span = null, // span of `binding_name` (set iff `binding_name` is)
|
||||
/// True when the optional binding was a backtick raw identifier
|
||||
/// (`` if `s2 := … ``) — exempt from the reserved-type-name check.
|
||||
/// (`` if `i2 := … ``) — exempt from the reserved-type-name check.
|
||||
binding_is_raw: bool = false,
|
||||
};
|
||||
|
||||
@@ -315,7 +315,7 @@ pub const MatchArm = struct {
|
||||
capture: ?[]const u8 = null, // payload binding name: case .variant: (name) { ... }
|
||||
capture_span: ?Span = null, // span of `capture` (set iff `capture` is)
|
||||
/// True when the capture was a backtick raw identifier
|
||||
/// (`` case .v: (`s2) ``) — exempt from the reserved-type-name check.
|
||||
/// (`` case .v: (`i2) ``) — exempt from the reserved-type-name check.
|
||||
capture_is_raw: bool = false,
|
||||
};
|
||||
|
||||
@@ -329,7 +329,7 @@ pub const ConstDecl = struct {
|
||||
/// 1:1 caret (the finding-1 bug).
|
||||
name_span: Span,
|
||||
/// True when the constant NAME was written as a backtick raw identifier
|
||||
/// (`` `s2 :: … ``). NO default: required at every site so the reserved-
|
||||
/// (`` `i2 :: … ``). NO default: required at every site so the reserved-
|
||||
/// name exemption can't be dropped — mirrors `checkBindingName`'s required
|
||||
/// `is_raw` argument so the parser and the check can't desync.
|
||||
is_raw: bool,
|
||||
@@ -344,7 +344,7 @@ pub const VarDecl = struct {
|
||||
foreign_lib: ?[]const u8 = null,
|
||||
foreign_name: ?[]const u8 = null,
|
||||
/// True when the binding name was written as a backtick raw identifier
|
||||
/// (`` `s2 := … ``). A raw name is exempt from the reserved-type-name
|
||||
/// (`` `i2 := … ``). A raw name is exempt from the reserved-type-name
|
||||
/// binding check.
|
||||
is_raw: bool = false,
|
||||
};
|
||||
@@ -378,7 +378,7 @@ pub const DestructureDecl = struct {
|
||||
names: []const []const u8,
|
||||
name_spans: []const Span, // one per entry in `names`, same order
|
||||
/// One per entry in `names`, same order: true when that target was a
|
||||
/// backtick raw identifier (`` `s2, b := … ``) — exempt from the
|
||||
/// backtick raw identifier (`` `i2, b := … ``) — exempt from the
|
||||
/// reserved-type-name binding check.
|
||||
name_is_raw: []const bool,
|
||||
value: *Node,
|
||||
@@ -392,7 +392,7 @@ pub const EnumDecl = struct {
|
||||
variant_values: []const ?*Node = &.{}, // explicit value per variant (null = auto), empty = all auto
|
||||
backing_type: ?*Node = null, // optional backing type: enum u8 { ... }
|
||||
/// True when the declared NAME was a backtick raw identifier
|
||||
/// (`` `s2 :: enum { … } ``) — exempt from the reserved-type-name decl
|
||||
/// (`` `i2 :: enum { … } ``) — exempt from the reserved-type-name decl
|
||||
/// check. A bare reserved-name decl still errors.
|
||||
is_raw: bool = false,
|
||||
};
|
||||
@@ -440,7 +440,7 @@ pub const StructDecl = struct {
|
||||
methods: []const *Node = &.{}, // fn_decl nodes for struct methods
|
||||
constants: []const *Node = &.{}, // const_decl nodes for struct-level constants
|
||||
/// True when the declared NAME was a backtick raw identifier
|
||||
/// (`` `s2 :: struct { … } ``) — exempt from the reserved-type-name decl
|
||||
/// (`` `i2 :: struct { … } ``) — exempt from the reserved-type-name decl
|
||||
/// check. A bare reserved-name decl still errors.
|
||||
is_raw: bool = false,
|
||||
};
|
||||
@@ -470,10 +470,10 @@ pub const TypeExpr = struct {
|
||||
is_generic: bool = false,
|
||||
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
|
||||
/// True when written as a backtick raw identifier in type position
|
||||
/// (`` `s2 ``). Such a reference is the LITERAL name `s2` used as a type —
|
||||
/// (`` `i2 ``). Such a reference is the LITERAL name `i2` used as a type —
|
||||
/// resolution skips the builtin/reserved classifier and looks up a
|
||||
/// `` `s2 ``-declared type (struct/enum/union/alias), else "unknown
|
||||
/// type". A bare `s2` keeps `is_raw = false` and is the int type.
|
||||
/// `` `i2 ``-declared type (struct/enum/union/alias), else "unknown
|
||||
/// type". A bare `i2` keeps `is_raw = false` and is the int type.
|
||||
is_raw: bool = false,
|
||||
};
|
||||
|
||||
@@ -523,7 +523,7 @@ pub const CatchExpr = struct {
|
||||
binding: ?[]const u8 = null,
|
||||
binding_span: ?Span = null, // span of `binding` (set iff `binding` is)
|
||||
/// True when the binding was a backtick raw identifier
|
||||
/// (`` x catch `s2 { … } ``) — exempt from the reserved-type-name check.
|
||||
/// (`` x catch `i2 { … } ``) — exempt from the reserved-type-name check.
|
||||
binding_is_raw: bool = false,
|
||||
body: *Node,
|
||||
is_match_body: bool = false,
|
||||
@@ -536,7 +536,7 @@ pub const OnFailStmt = struct {
|
||||
binding: ?[]const u8 = null,
|
||||
binding_span: ?Span = null, // span of `binding` (set iff `binding` is)
|
||||
/// True when the binding was a backtick raw identifier
|
||||
/// (`` onfail `s2 { … } ``) — exempt from the reserved-type-name check.
|
||||
/// (`` onfail `i2 { … } ``) — exempt from the reserved-type-name check.
|
||||
binding_is_raw: bool = false,
|
||||
body: *Node,
|
||||
};
|
||||
@@ -562,7 +562,7 @@ pub const ImportDecl = struct {
|
||||
path: []const u8,
|
||||
name: ?[]const u8,
|
||||
/// True when the namespace NAME was a backtick raw identifier
|
||||
/// (`` `s2 :: #import "…" ``) — exempt from the reserved-type-name decl
|
||||
/// (`` `i2 :: #import "…" ``) — exempt from the reserved-type-name decl
|
||||
/// check. A flat `#import` (name == null) binds nothing.
|
||||
is_raw: bool = false,
|
||||
};
|
||||
@@ -585,10 +585,10 @@ pub const ParameterizedTypeExpr = struct {
|
||||
name: []const u8, // e.g. "Vector", or later generic struct names
|
||||
args: []const *Node, // e.g. [int_literal(3), type_expr("f32")]
|
||||
/// True when the base name was a backtick raw identifier in type position
|
||||
/// (`` `s2(s64) ``). Such a reference is the LITERAL name `s2` used as a
|
||||
/// (`` `i2(i64) ``). Such a reference is the LITERAL name `i2` used as a
|
||||
/// parameterized type — resolution skips the builtin parameterized
|
||||
/// classifier (e.g. the `Vector` intrinsic) and instantiates a
|
||||
/// `` `s2 ``-declared generic template.
|
||||
/// `` `i2 ``-declared generic template.
|
||||
is_raw: bool = false,
|
||||
};
|
||||
|
||||
@@ -647,7 +647,7 @@ pub const WhileExpr = struct {
|
||||
binding_name: ?[]const u8 = null, // for `while val := expr { ... }` optional binding
|
||||
binding_span: ?Span = null, // span of `binding_name` (set iff `binding_name` is)
|
||||
/// True when the optional binding was a backtick raw identifier
|
||||
/// (`` while `s2 := … ``) — exempt from the reserved-type-name check.
|
||||
/// (`` while `i2 := … ``) — exempt from the reserved-type-name check.
|
||||
binding_is_raw: bool = false,
|
||||
};
|
||||
|
||||
@@ -674,7 +674,7 @@ pub const ForIterable = struct {
|
||||
pub const ForCapture = struct {
|
||||
name: []const u8,
|
||||
span: ?Span = null,
|
||||
/// True when the name was a backtick raw identifier (`` for xs (`s2) ``)
|
||||
/// True when the name was a backtick raw identifier (`` for xs (`i2) ``)
|
||||
/// — exempt from the reserved-type-name check.
|
||||
is_raw: bool = false,
|
||||
/// `(*x)` — bind a pointer into the collection (no per-element copy).
|
||||
|
||||
@@ -47,7 +47,7 @@ pub const AbiLowering = struct {
|
||||
}
|
||||
|
||||
// WASM32: usize/isize are pointer-sized (i32 on wasm32).
|
||||
// Other integer types (s64, u64) keep their declared size — they represent
|
||||
// Other integer types (i64, u64) keep their declared size — they represent
|
||||
// genuinely 64-bit values (SDL_WindowFlags, timestamps, etc.).
|
||||
if (self.e.target_config.isWasm32()) {
|
||||
if (ir_ty == .usize or ir_ty == .isize) return self.e.cached_i32;
|
||||
|
||||
@@ -338,7 +338,7 @@ pub const Ops = struct {
|
||||
const result = c.LLVMBuildLoad2(self.e.builder, llvm_ty, ptr, "load");
|
||||
self.e.mapRef(result);
|
||||
} else {
|
||||
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(if (instruction.ty == .void) .s64 else instruction.ty)));
|
||||
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(if (instruction.ty == .void) .i64 else instruction.ty)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,8 +574,8 @@ pub const Ops = struct {
|
||||
if (is_pointer_ret) break :blk emit.Jni.CallStaticObjectMethod;
|
||||
break :blk switch (ret_ty_id) {
|
||||
.void => emit.Jni.CallStaticVoidMethod,
|
||||
.s32 => emit.Jni.CallStaticIntMethod,
|
||||
.s64 => emit.Jni.CallStaticLongMethod,
|
||||
.i32 => emit.Jni.CallStaticIntMethod,
|
||||
.i64 => emit.Jni.CallStaticLongMethod,
|
||||
.f32 => emit.Jni.CallStaticFloatMethod,
|
||||
.f64 => emit.Jni.CallStaticDoubleMethod,
|
||||
.bool => emit.Jni.CallStaticBooleanMethod,
|
||||
@@ -588,8 +588,8 @@ pub const Ops = struct {
|
||||
if (is_pointer_ret) break :blk emit.Jni.CallNonvirtualObjectMethod;
|
||||
break :blk switch (ret_ty_id) {
|
||||
.void => emit.Jni.CallNonvirtualVoidMethod,
|
||||
.s32 => emit.Jni.CallNonvirtualIntMethod,
|
||||
.s64 => emit.Jni.CallNonvirtualLongMethod,
|
||||
.i32 => emit.Jni.CallNonvirtualIntMethod,
|
||||
.i64 => emit.Jni.CallNonvirtualLongMethod,
|
||||
.f32 => emit.Jni.CallNonvirtualFloatMethod,
|
||||
.f64 => emit.Jni.CallNonvirtualDoubleMethod,
|
||||
.bool => emit.Jni.CallNonvirtualBooleanMethod,
|
||||
@@ -602,8 +602,8 @@ pub const Ops = struct {
|
||||
if (is_pointer_ret) break :blk emit.Jni.CallObjectMethod;
|
||||
break :blk switch (ret_ty_id) {
|
||||
.void => emit.Jni.CallVoidMethod,
|
||||
.s32 => emit.Jni.CallIntMethod,
|
||||
.s64 => emit.Jni.CallLongMethod,
|
||||
.i32 => emit.Jni.CallIntMethod,
|
||||
.i64 => emit.Jni.CallLongMethod,
|
||||
.f32 => emit.Jni.CallFloatMethod,
|
||||
.f64 => emit.Jni.CallDoubleMethod,
|
||||
.bool => emit.Jni.CallBooleanMethod,
|
||||
@@ -983,7 +983,7 @@ pub const Ops = struct {
|
||||
/// `const_type` / `type_of` produce) → the TypeId is the payload.
|
||||
/// Otherwise the box carries a *runtime value* whose type IS the tag
|
||||
/// → use the tag as the TypeId. This is what makes `type_name(av)`
|
||||
/// for `av : Any = 6` report `s64` (the held value's type), while
|
||||
/// for `av : Any = 6` report `i64` (the held value's type), while
|
||||
/// `type_name(type_of(x))` still names the held type.
|
||||
/// `.unresolved` is a hard tripwire: a type-resolution failure reached
|
||||
/// emission without a diagnostic.
|
||||
@@ -1059,7 +1059,7 @@ pub const Ops = struct {
|
||||
.type_eq => {
|
||||
// Dynamic `type_eq(a, b)` — both args are
|
||||
// Type values. Extract TypeId from each Any
|
||||
// box (or use directly if `.s64`-typed),
|
||||
// box (or use directly if `.i64`-typed),
|
||||
// icmp eq.
|
||||
const a = blk: {
|
||||
const v = self.e.resolveRef(bi.args[0]);
|
||||
|
||||
@@ -21,10 +21,10 @@ pub const TypeLowering = struct {
|
||||
return switch (ty) {
|
||||
.void => self.e.cached_void,
|
||||
.bool => self.e.cached_i1,
|
||||
.s8 => self.e.cached_i8,
|
||||
.s16 => self.e.cached_i16,
|
||||
.s32 => self.e.cached_i32,
|
||||
.s64 => self.e.cached_i64,
|
||||
.i8 => self.e.cached_i8,
|
||||
.i16 => self.e.cached_i16,
|
||||
.i32 => self.e.cached_i32,
|
||||
.i64 => self.e.cached_i64,
|
||||
.u8 => self.e.cached_i8,
|
||||
.u16 => self.e.cached_i16,
|
||||
.u32 => self.e.cached_i32,
|
||||
|
||||
@@ -127,7 +127,7 @@ pub fn processCImport(
|
||||
.name = pname,
|
||||
.name_span = .{ .start = 0, .end = 0 },
|
||||
.type_expr = ptype_node,
|
||||
// Foreign C param names (`s1`, `s2`, …) are RAW — exempt from
|
||||
// Foreign C param names (`i1`, `i2`, …) are RAW — exempt from
|
||||
// the reserved-type-name binding check; generated bindings
|
||||
// must import without hand-edits.
|
||||
.is_raw = true,
|
||||
@@ -157,7 +157,7 @@ pub fn processCImport(
|
||||
.return_type = ret_node,
|
||||
.body = foreign_body,
|
||||
// A foreign C function whose own NAME collides with a reserved
|
||||
// type spelling (`int s2(int);`) is RAW — exempt from the
|
||||
// type spelling (`int i2(int);`) is RAW — exempt from the
|
||||
// reserved-type-name decl check so generated bindings import
|
||||
// without hand-edits.
|
||||
.is_raw = true,
|
||||
@@ -505,9 +505,9 @@ fn mapCTypeToSxNode(
|
||||
if (std.mem.eql(u8, base, "void") or std.mem.eql(u8, base, "const void")) {
|
||||
return makePointerTypeNode(allocator, "void");
|
||||
}
|
||||
// int * → *s32
|
||||
// int * → *i32
|
||||
if (std.mem.eql(u8, base, "int") or std.mem.eql(u8, base, "const int")) {
|
||||
return makePointerTypeNode(allocator, "s32");
|
||||
return makePointerTypeNode(allocator, "i32");
|
||||
}
|
||||
// unsigned int * / unsigned * → *u32
|
||||
if (std.mem.eql(u8, base, "unsigned int") or std.mem.eql(u8, base, "unsigned") or std.mem.eql(u8, base, "const unsigned int")) {
|
||||
@@ -521,9 +521,9 @@ fn mapCTypeToSxNode(
|
||||
if (std.mem.eql(u8, base, "double") or std.mem.eql(u8, base, "const double")) {
|
||||
return makePointerTypeNode(allocator, "f64");
|
||||
}
|
||||
// short * → *s16
|
||||
// short * → *i16
|
||||
if (std.mem.eql(u8, base, "short") or std.mem.eql(u8, base, "const short")) {
|
||||
return makePointerTypeNode(allocator, "s16");
|
||||
return makePointerTypeNode(allocator, "i16");
|
||||
}
|
||||
// Pointer to pointer → *void
|
||||
if (std.mem.endsWith(u8, base, "*")) {
|
||||
@@ -539,13 +539,13 @@ fn mapCTypeToSxNode(
|
||||
}
|
||||
|
||||
// Direct types
|
||||
if (std.mem.eql(u8, trimmed, "int") or std.mem.eql(u8, trimmed, "signed int")) return makeTypeExprNode(allocator, "s32");
|
||||
if (std.mem.eql(u8, trimmed, "int") or std.mem.eql(u8, trimmed, "signed int")) return makeTypeExprNode(allocator, "i32");
|
||||
if (std.mem.eql(u8, trimmed, "unsigned int") or std.mem.eql(u8, trimmed, "unsigned")) return makeTypeExprNode(allocator, "u32");
|
||||
if (std.mem.eql(u8, trimmed, "long") or std.mem.eql(u8, trimmed, "long int") or std.mem.eql(u8, trimmed, "signed long")) return makeTypeExprNode(allocator, "s64");
|
||||
if (std.mem.eql(u8, trimmed, "long") or std.mem.eql(u8, trimmed, "long int") or std.mem.eql(u8, trimmed, "signed long")) return makeTypeExprNode(allocator, "i64");
|
||||
if (std.mem.eql(u8, trimmed, "unsigned long") or std.mem.eql(u8, trimmed, "unsigned long int")) return makeTypeExprNode(allocator, "u64");
|
||||
if (std.mem.eql(u8, trimmed, "long long") or std.mem.eql(u8, trimmed, "long long int")) return makeTypeExprNode(allocator, "s64");
|
||||
if (std.mem.eql(u8, trimmed, "long long") or std.mem.eql(u8, trimmed, "long long int")) return makeTypeExprNode(allocator, "i64");
|
||||
if (std.mem.eql(u8, trimmed, "unsigned long long") or std.mem.eql(u8, trimmed, "unsigned long long int")) return makeTypeExprNode(allocator, "u64");
|
||||
if (std.mem.eql(u8, trimmed, "short") or std.mem.eql(u8, trimmed, "short int") or std.mem.eql(u8, trimmed, "signed short")) return makeTypeExprNode(allocator, "s16");
|
||||
if (std.mem.eql(u8, trimmed, "short") or std.mem.eql(u8, trimmed, "short int") or std.mem.eql(u8, trimmed, "signed short")) return makeTypeExprNode(allocator, "i16");
|
||||
if (std.mem.eql(u8, trimmed, "unsigned short") or std.mem.eql(u8, trimmed, "unsigned short int")) return makeTypeExprNode(allocator, "u16");
|
||||
if (std.mem.eql(u8, trimmed, "char") or std.mem.eql(u8, trimmed, "signed char")) return makeTypeExprNode(allocator, "u8");
|
||||
if (std.mem.eql(u8, trimmed, "unsigned char")) return makeTypeExprNode(allocator, "u8");
|
||||
@@ -554,8 +554,8 @@ fn mapCTypeToSxNode(
|
||||
if (std.mem.eql(u8, trimmed, "size_t")) return makeTypeExprNode(allocator, "u64");
|
||||
if (std.mem.eql(u8, trimmed, "_Bool") or std.mem.eql(u8, trimmed, "bool")) return makeTypeExprNode(allocator, "u8");
|
||||
|
||||
// Default: unknown type → s64 (treat as opaque integer-sized value)
|
||||
return makeTypeExprNode(allocator, "s64");
|
||||
// Default: unknown type → i64 (treat as opaque integer-sized value)
|
||||
return makeTypeExprNode(allocator, "i64");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -83,14 +83,14 @@ test "imports: module_decls retains same-name cross-module fns; flat_import_grap
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "greet :: () -> s64 { 1 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "greet :: () -> s64 { 2 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "nsmod.sx", .data = "helper :: () -> s64 { 3 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "greet :: () -> i64 { 1 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "greet :: () -> i64 { 2 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "nsmod.sx", .data = "helper :: () -> i64 { 3 }\n" });
|
||||
const main_src =
|
||||
\\#import "a.sx";
|
||||
\\#import "b.sx";
|
||||
\\ns :: #import "nsmod.sx";
|
||||
\\main :: () -> s32 { 0 }
|
||||
\\main :: () -> i32 { 0 }
|
||||
\\
|
||||
;
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = main_src });
|
||||
@@ -192,12 +192,12 @@ test "imports: mixed non-fn/fn same-name collision stays first-wins in merged sc
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "Widget :: struct { x: s64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "Widget :: () -> s64 { 7 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "Widget :: struct { x: i64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "Widget :: () -> i64 { 7 }\n" });
|
||||
const main_src =
|
||||
\\#import "a.sx";
|
||||
\\#import "b.sx";
|
||||
\\main :: () -> s32 { 0 }
|
||||
\\main :: () -> i32 { 0 }
|
||||
\\
|
||||
;
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = main_src });
|
||||
@@ -263,10 +263,10 @@ test "buildImportFacts: flat imports keep same-name fn/struct + value-vs-type pe
|
||||
defer tmp.cleanup();
|
||||
|
||||
// a.sx: dup() fn, Box struct, Shape as a VALUE const.
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "dup :: () -> s64 { 1 }\nBox :: struct { x: s64 }\nShape :: 7;\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "dup :: () -> i64 { 1 }\nBox :: struct { x: i64 }\nShape :: 7;\n" });
|
||||
// b.sx: dup() fn, Box struct, Shape as a TYPE (same spelling as a.sx's value).
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "dup :: () -> s64 { 2 }\nBox :: struct { y: s64 }\nShape :: struct { z: s64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "#import \"a.sx\";\n#import \"b.sx\";\nmain :: () -> s32 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "dup :: () -> i64 { 2 }\nBox :: struct { y: i64 }\nShape :: struct { z: i64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "#import \"a.sx\";\n#import \"b.sx\";\nmain :: () -> i32 { 0 }\n" });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
@@ -321,9 +321,9 @@ test "buildImportFacts: directory import unions member-file decls under the dir
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.createDirPath(io, "lib");
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "lib/one.sx", .data = "from_one :: () -> s64 { 1 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "lib/two.sx", .data = "Two :: struct { v: s64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "#import \"lib\";\nmain :: () -> s32 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "lib/one.sx", .data = "from_one :: () -> i64 { 1 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "lib/two.sx", .data = "Two :: struct { v: i64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "#import \"lib\";\nmain :: () -> i32 { 0 }\n" });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
@@ -349,8 +349,8 @@ test "buildImportFacts: namespaced file import captures target_module_path" {
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "point.sx", .data = "Point :: struct { x: s64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "g :: #import \"point.sx\";\nmain :: () -> s32 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "point.sx", .data = "Point :: struct { x: i64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "g :: #import \"point.sx\";\nmain :: () -> i32 { 0 }\n" });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
@@ -382,8 +382,8 @@ test "buildImportFacts: namespaced directory import captures dir path as target"
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.createDirPath(io, "pkg");
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "pkg/m.sx", .data = "helper :: () -> s64 { 9 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "pkg :: #import \"pkg\";\nmain :: () -> s32 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "pkg/m.sx", .data = "helper :: () -> i64 { 9 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "pkg :: #import \"pkg\";\nmain :: () -> i32 { 0 }\n" });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
@@ -409,7 +409,7 @@ test "buildImportFacts: c-import namespace recorded as an edge" {
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "ch.h", .data = "int cm_add(int a, int b);\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "cmod :: #import c {\n #include \"ch.h\";\n};\nmain :: () -> s32 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "cmod :: #import c {\n #include \"ch.h\";\n};\nmain :: () -> i32 { 0 }\n" });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
@@ -440,7 +440,7 @@ test "buildImportFacts: same-module duplicate top-level name is diagnosed" {
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "foo :: () -> s64 { 1 }\nfoo :: () -> s64 { 2 }\nmain :: () -> s32 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "foo :: () -> i64 { 1 }\nfoo :: () -> i64 { 2 }\nmain :: () -> i32 { 0 }\n" });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
@@ -469,8 +469,8 @@ test "buildImportFacts: fn-then-namespace-alias same-module collision is diagnos
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "lib.sx", .data = "helper :: () -> s64 { 9 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "dup :: () -> s64 { 1 }\ndup :: #import \"lib.sx\";\nmain :: () -> s32 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "lib.sx", .data = "helper :: () -> i64 { 9 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "dup :: () -> i64 { 1 }\ndup :: #import \"lib.sx\";\nmain :: () -> i32 { 0 }\n" });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
@@ -493,8 +493,8 @@ test "buildImportFacts: namespace-alias-then-fn same-module collision is diagnos
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "lib.sx", .data = "helper :: () -> s64 { 9 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "dup :: #import \"lib.sx\";\ndup :: () -> s64 { 1 }\nmain :: () -> s32 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "lib.sx", .data = "helper :: () -> i64 { 9 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "dup :: #import \"lib.sx\";\ndup :: () -> i64 { 1 }\nmain :: () -> i32 { 0 }\n" });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
@@ -523,9 +523,9 @@ test "buildDeclTable: stable DeclId per decl, round-trip, struct keying, namespa
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "lib.sx", .data = "helper :: () -> s64 { 9 }\nBox :: struct($T: Type) { v: T; }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "geom.sx", .data = "Point :: struct { x: s64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "#import \"lib.sx\";\ng :: #import \"geom.sx\";\nmain :: () -> s32 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "lib.sx", .data = "helper :: () -> i64 { 9 }\nBox :: struct($T: Type) { v: T; }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "geom.sx", .data = "Point :: struct { x: i64 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "#import \"lib.sx\";\ng :: #import \"geom.sx\";\nmain :: () -> i32 { 0 }\n" });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
|
||||
@@ -72,16 +72,16 @@ test "calls: builtin and reflection result types, unknown fallthrough" {
|
||||
var args = [_]*Node{&arg};
|
||||
|
||||
const cases = [_]struct { name: []const u8, want: TypeId }{
|
||||
.{ .name = "size_of", .want = .s64 },
|
||||
.{ .name = "align_of", .want = .s64 },
|
||||
.{ .name = "size_of", .want = .i64 },
|
||||
.{ .name = "align_of", .want = .i64 },
|
||||
// Reflection builtins (resolved by callee name, outside the
|
||||
// `resolveBuiltin` table) — each must keep its own result tag so a
|
||||
// pack-fn caller boxes the value with the right type.
|
||||
.{ .name = "type_name", .want = .string },
|
||||
.{ .name = "type_eq", .want = .bool },
|
||||
.{ .name = "has_impl", .want = .bool },
|
||||
.{ .name = "field_count", .want = .s64 },
|
||||
.{ .name = "field_index", .want = .s64 },
|
||||
.{ .name = "field_count", .want = .i64 },
|
||||
.{ .name = "field_index", .want = .i64 },
|
||||
.{ .name = "field_name", .want = .string },
|
||||
.{ .name = "error_tag_name", .want = .string },
|
||||
.{ .name = "is_comptime", .want = .bool },
|
||||
@@ -110,15 +110,15 @@ test "calls: cast result type is its resolved type argument" {
|
||||
defer module.deinit();
|
||||
var l = Lowering.init(&module);
|
||||
|
||||
// `cast(s64) x` types as the resolved target type — the first arg is the
|
||||
// `cast(i64) x` types as the resolved target type — the first arg is the
|
||||
// type expression, resolved via `resolveTypeArg` (a primitive needs no
|
||||
// scope / registration).
|
||||
var target = node(.{ .type_expr = .{ .name = "s64" } });
|
||||
var target = node(.{ .type_expr = .{ .name = "i64" } });
|
||||
var value = node(.{ .int_literal = .{ .value = 1 } });
|
||||
var cast_args = [_]*Node{ &target, &value };
|
||||
var cast_callee = node(.{ .identifier = .{ .name = "cast" } });
|
||||
var cast_call = node(.{ .call = .{ .callee = &cast_callee, .args = &cast_args } });
|
||||
try std.testing.expectEqual(TypeId.s64, l.inferExprType(&cast_call));
|
||||
try std.testing.expectEqual(TypeId.i64, l.inferExprType(&cast_call));
|
||||
}
|
||||
|
||||
test "calls: dot-shorthand enum construction types as the target type" {
|
||||
@@ -136,8 +136,8 @@ test "calls: dot-shorthand enum construction types as the target type" {
|
||||
|
||||
try std.testing.expectEqual(TypeId.unresolved, l.inferExprType(&enum_call));
|
||||
|
||||
l.target_type = .s32;
|
||||
try std.testing.expectEqual(TypeId.s32, l.inferExprType(&enum_call));
|
||||
l.target_type = .i32;
|
||||
try std.testing.expectEqual(TypeId.i32, l.inferExprType(&enum_call));
|
||||
}
|
||||
|
||||
// ── Layer 2: the CallPlan object (kind / target / variant / properties) ─────
|
||||
@@ -157,7 +157,7 @@ test "plan: builtin and reflection carry kind + target" {
|
||||
const so = cr.plan(&so_call.data.call);
|
||||
try std.testing.expectEqual(CallPlan.Kind.builtin, so.kind);
|
||||
try std.testing.expectEqual(BuiltinId.size_of, so.target.builtin);
|
||||
try std.testing.expectEqual(TypeId.s64, so.return_type);
|
||||
try std.testing.expectEqual(TypeId.i64, so.return_type);
|
||||
|
||||
var tn_callee = node(.{ .identifier = .{ .name = "type_name" } });
|
||||
var tn_call = node(.{ .call = .{ .callee = &tn_callee, .args = &args } });
|
||||
@@ -190,13 +190,13 @@ test "plan: lazy free fn classifies as direct_fn and flags default-arg expansion
|
||||
var l = Lowering.init(&module);
|
||||
const cr = CallResolver{ .l = &l };
|
||||
|
||||
// greet :: (a: s64, b: s64 = 0) -> s64 — registered but NOT lowered, so
|
||||
// greet :: (a: i64, b: i64 = 0) -> i64 — registered but NOT lowered, so
|
||||
// it resolves through the AST (lazy) arm and `b`'s default is splice-able.
|
||||
const params = [_]ast.Param{
|
||||
.{ .name = "a", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeExpr(alloc, "s64") },
|
||||
.{ .name = "b", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeExpr(alloc, "s64"), .default_expr = intLit(alloc, 0) },
|
||||
.{ .name = "a", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeExpr(alloc, "i64") },
|
||||
.{ .name = "b", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeExpr(alloc, "i64"), .default_expr = intLit(alloc, 0) },
|
||||
};
|
||||
const fd = ast.FnDecl{ .name = "greet", .params = ¶ms, .return_type = typeExpr(alloc, "s64"), .body = emptyBody(alloc) };
|
||||
const fd = ast.FnDecl{ .name = "greet", .params = ¶ms, .return_type = typeExpr(alloc, "i64"), .body = emptyBody(alloc) };
|
||||
l.program_index.fn_ast_map.put("greet", &fd) catch unreachable;
|
||||
|
||||
// greet(1) — omits `b`, so its default is spliced in.
|
||||
@@ -206,7 +206,7 @@ test "plan: lazy free fn classifies as direct_fn and flags default-arg expansion
|
||||
const p = cr.plan(&call.data.call);
|
||||
try std.testing.expectEqual(CallPlan.Kind.direct_fn, p.kind);
|
||||
try std.testing.expectEqualStrings("greet", p.target.named);
|
||||
try std.testing.expectEqual(TypeId.s64, p.return_type);
|
||||
try std.testing.expectEqual(TypeId.i64, p.return_type);
|
||||
try std.testing.expect(p.expands_defaults);
|
||||
try std.testing.expect(!p.prepends_receiver);
|
||||
}
|
||||
@@ -271,19 +271,19 @@ test "plan: closure and fn-pointer callees, __sx_ctx by calling convention" {
|
||||
try std.testing.expect(p.prepends_ctx);
|
||||
}
|
||||
|
||||
// fp : () -> s32 (default conv) — sx fn-pointer, carries ctx.
|
||||
const fp_ty = module.types.functionType(&.{}, .s32);
|
||||
// fp : () -> i32 (default conv) — sx fn-pointer, carries ctx.
|
||||
const fp_ty = module.types.functionType(&.{}, .i32);
|
||||
scope.put("fp", .{ .ref = Ref.none, .ty = fp_ty, .is_alloca = false });
|
||||
{
|
||||
const call = callNode(alloc, ident(alloc, "fp"), &.{});
|
||||
const p = cr.plan(&call.data.call);
|
||||
try std.testing.expectEqual(CallPlan.Kind.fn_pointer, p.kind);
|
||||
try std.testing.expectEqual(TypeId.s32, p.return_type);
|
||||
try std.testing.expectEqual(TypeId.i32, p.return_type);
|
||||
try std.testing.expect(p.prepends_ctx);
|
||||
}
|
||||
|
||||
// cfp : () -> s32 (C conv) — C fn-pointer, NO implicit ctx.
|
||||
const cfp_ty = module.types.functionTypeCC(&.{}, .s32, .c);
|
||||
// cfp : () -> i32 (C conv) — C fn-pointer, NO implicit ctx.
|
||||
const cfp_ty = module.types.functionTypeCC(&.{}, .i32, .c);
|
||||
scope.put("cfp", .{ .ref = Ref.none, .ty = cfp_ty, .is_alloca = false });
|
||||
{
|
||||
const call = callNode(alloc, ident(alloc, "cfp"), &.{});
|
||||
@@ -302,9 +302,9 @@ test "plan: protocol dispatch selects method index + prepends receiver" {
|
||||
var l = Lowering.init(&module);
|
||||
const cr = CallResolver{ .l = &l };
|
||||
|
||||
// Drawable :: protocol { measure :: () -> s64; draw :: () -> bool; }
|
||||
// Drawable :: protocol { measure :: () -> i64; draw :: () -> bool; }
|
||||
const methods = [_]ast.ProtocolMethodDecl{
|
||||
.{ .name = "measure", .params = &.{}, .param_names = &.{}, .return_type = typeExpr(alloc, "s64"), .default_body = null },
|
||||
.{ .name = "measure", .params = &.{}, .param_names = &.{}, .return_type = typeExpr(alloc, "i64"), .default_body = null },
|
||||
.{ .name = "draw", .params = &.{}, .param_names = &.{}, .return_type = typeExpr(alloc, "bool"), .default_body = null },
|
||||
};
|
||||
const pd = ast.ProtocolDecl{ .name = "Drawable", .methods = &methods };
|
||||
@@ -329,12 +329,12 @@ test "plan: struct (UFCS) method via #compiler dispatch + prepends receiver" {
|
||||
var l = Lowering.init(&module);
|
||||
const cr = CallResolver{ .l = &l };
|
||||
|
||||
// struct Point, with a `#compiler` method Point.scale(self) -> s64.
|
||||
// struct Point, with a `#compiler` method Point.scale(self) -> i64.
|
||||
_ = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Point"), .fields = &.{} } });
|
||||
const self_param = ast.Param{ .name = "self", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeExpr(alloc, "Point") };
|
||||
const params = [_]ast.Param{self_param};
|
||||
const compiler_body = mk(alloc, .{ .compiler_expr = {} });
|
||||
const method_fd = ast.FnDecl{ .name = "Point.scale", .params = ¶ms, .return_type = typeExpr(alloc, "s64"), .body = compiler_body };
|
||||
const method_fd = ast.FnDecl{ .name = "Point.scale", .params = ¶ms, .return_type = typeExpr(alloc, "i64"), .body = compiler_body };
|
||||
l.program_index.fn_ast_map.put("Point.scale", &method_fd) catch unreachable;
|
||||
|
||||
const recv = callNode(alloc, ident(alloc, "cast"), &[_]*Node{ typeExpr(alloc, "Point"), intLit(alloc, 0) });
|
||||
@@ -342,7 +342,7 @@ test "plan: struct (UFCS) method via #compiler dispatch + prepends receiver" {
|
||||
const p = cr.plan(&call.data.call);
|
||||
try std.testing.expectEqual(CallPlan.Kind.struct_method, p.kind);
|
||||
try std.testing.expectEqualStrings("Point.scale", p.target.named);
|
||||
try std.testing.expectEqual(TypeId.s64, p.return_type);
|
||||
try std.testing.expectEqual(TypeId.i64, p.return_type);
|
||||
try std.testing.expect(p.prepends_receiver);
|
||||
}
|
||||
|
||||
@@ -356,8 +356,8 @@ test "plan: foreign-class instance vs static dispatch" {
|
||||
const cr = CallResolver{ .l = &l };
|
||||
|
||||
const members = [_]ast.ForeignClassMember{
|
||||
.{ .method = .{ .name = "length", .params = &.{}, .param_names = &.{}, .return_type = typeExpr(alloc, "s64"), .is_static = false } },
|
||||
.{ .method = .{ .name = "stringWithUTF8String", .params = &.{}, .param_names = &.{}, .return_type = typeExpr(alloc, "s64"), .is_static = true } },
|
||||
.{ .method = .{ .name = "length", .params = &.{}, .param_names = &.{}, .return_type = typeExpr(alloc, "i64"), .is_static = false } },
|
||||
.{ .method = .{ .name = "stringWithUTF8String", .params = &.{}, .param_names = &.{}, .return_type = typeExpr(alloc, "i64"), .is_static = true } },
|
||||
};
|
||||
var fcd = ast.ForeignClassDecl{ .name = "NSString", .foreign_path = "NSString", .runtime = .objc_class, .members = &members };
|
||||
l.program_index.foreign_class_map.put("NSString", &fcd) catch unreachable;
|
||||
@@ -371,7 +371,7 @@ test "plan: foreign-class instance vs static dispatch" {
|
||||
try std.testing.expectEqual(CallPlan.Kind.foreign_instance, p.kind);
|
||||
try std.testing.expectEqualStrings("length", p.target.foreign_method.name);
|
||||
try std.testing.expect(!p.target.foreign_method.is_static);
|
||||
try std.testing.expectEqual(TypeId.s64, p.return_type);
|
||||
try std.testing.expectEqual(TypeId.i64, p.return_type);
|
||||
try std.testing.expect(p.prepends_receiver);
|
||||
}
|
||||
// Static: `NSString.stringWithUTF8String(...)` — no receiver.
|
||||
@@ -430,7 +430,7 @@ test "plan: free-function UFCS prepends receiver, distinct from namespace_fn" {
|
||||
const cr = CallResolver{ .l = &l };
|
||||
|
||||
// struct Counter, and a FREE ufcs function `bump :: ufcs (c: Counter) ->
|
||||
// s32` — NOT registered as `Counter.bump`, so it can only be reached via
|
||||
// i32` — NOT registered as `Counter.bump`, so it can only be reached via
|
||||
// UFCS. Dot-dispatch is OPT-IN: the fn carries `is_ufcs` and is
|
||||
// registered in `fn_ast_map`, where the plan's opt-in gate reads it.
|
||||
const counter = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Counter"), .fields = &.{} } });
|
||||
@@ -438,7 +438,7 @@ test "plan: free-function UFCS prepends receiver, distinct from namespace_fn" {
|
||||
const params = [_]ast.Param{c_param};
|
||||
const ret_stmt = mk(alloc, .{ .return_stmt = .{ .value = intLit(alloc, 7) } });
|
||||
const body = mk(alloc, .{ .block = .{ .stmts = &[_]*Node{ret_stmt} } });
|
||||
const fd = ast.FnDecl{ .name = "bump", .params = ¶ms, .return_type = typeExpr(alloc, "s32"), .body = body, .is_ufcs = true };
|
||||
const fd = ast.FnDecl{ .name = "bump", .params = ¶ms, .return_type = typeExpr(alloc, "i32"), .body = body, .is_ufcs = true };
|
||||
l.program_index.fn_ast_map.put("bump", &fd) catch unreachable;
|
||||
l.lowerFunction(&fd, "bump", false);
|
||||
const fid = l.resolveFuncByName("bump").?;
|
||||
@@ -457,7 +457,7 @@ test "plan: free-function UFCS prepends receiver, distinct from namespace_fn" {
|
||||
try std.testing.expectEqual(fid, p.target.func);
|
||||
try std.testing.expect(p.prepends_receiver);
|
||||
try std.testing.expect(p.prepends_ctx);
|
||||
try std.testing.expectEqual(TypeId.s32, p.return_type);
|
||||
try std.testing.expectEqual(TypeId.i32, p.return_type);
|
||||
}
|
||||
|
||||
test "plan: qualified namespace function" {
|
||||
@@ -469,13 +469,13 @@ test "plan: qualified namespace function" {
|
||||
var l = Lowering.init(&module);
|
||||
const cr = CallResolver{ .l = &l };
|
||||
|
||||
// mathlib.square :: () -> s64 — registered under its qualified name, lazy.
|
||||
const fd = ast.FnDecl{ .name = "mathlib.square", .params = &.{}, .return_type = typeExpr(alloc, "s64"), .body = emptyBody(alloc) };
|
||||
// mathlib.square :: () -> i64 — registered under its qualified name, lazy.
|
||||
const fd = ast.FnDecl{ .name = "mathlib.square", .params = &.{}, .return_type = typeExpr(alloc, "i64"), .body = emptyBody(alloc) };
|
||||
l.program_index.fn_ast_map.put("mathlib.square", &fd) catch unreachable;
|
||||
|
||||
const call = callNode(alloc, fieldAccess(alloc, ident(alloc, "mathlib"), "square"), &.{});
|
||||
const p = cr.plan(&call.data.call);
|
||||
try std.testing.expectEqual(CallPlan.Kind.namespace_fn, p.kind);
|
||||
try std.testing.expectEqualStrings("mathlib.square", p.target.named);
|
||||
try std.testing.expectEqual(TypeId.s64, p.return_type);
|
||||
try std.testing.expectEqual(TypeId.i64, p.return_type);
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ pub const CallResolver = struct {
|
||||
}
|
||||
break :blk TypeId.f64;
|
||||
},
|
||||
.size_of, .align_of => .s64,
|
||||
.size_of, .align_of => .i64,
|
||||
.cast => if (c.args.len > 0) self.l.resolveTypeArg(c.args[0]) else .unresolved,
|
||||
else => .unresolved,
|
||||
};
|
||||
@@ -144,8 +144,8 @@ pub const CallResolver = struct {
|
||||
if (std.mem.eql(u8, bare_name, "type_name")) return refl(bare_name, .string);
|
||||
if (std.mem.eql(u8, bare_name, "type_eq")) return refl(bare_name, .bool);
|
||||
if (std.mem.eql(u8, bare_name, "has_impl")) return refl(bare_name, .bool);
|
||||
if (std.mem.eql(u8, bare_name, "field_count")) return refl(bare_name, .s64);
|
||||
if (std.mem.eql(u8, bare_name, "field_index")) return refl(bare_name, .s64);
|
||||
if (std.mem.eql(u8, bare_name, "field_count")) return refl(bare_name, .i64);
|
||||
if (std.mem.eql(u8, bare_name, "field_index")) return refl(bare_name, .i64);
|
||||
if (std.mem.eql(u8, bare_name, "field_name")) return refl(bare_name, .string);
|
||||
if (std.mem.eql(u8, bare_name, "error_tag_name")) return refl(bare_name, .string);
|
||||
if (std.mem.eql(u8, bare_name, "is_comptime")) return refl(bare_name, .bool);
|
||||
@@ -351,7 +351,7 @@ pub const CallResolver = struct {
|
||||
// lowering dispatches — they can't disagree under a flat same-name
|
||||
// collision (R5 §C). Without this, plan typed the
|
||||
// first-wins winner while lowering bound the selected shadow,
|
||||
// mis-tagging the call's result (a string-typed winner over an s64
|
||||
// mis-tagging the call's result (a string-typed winner over an i64
|
||||
// shadow boxes a raw int as a string pointer → segfault).
|
||||
// `.ambiguous` / `.none` fall through to the first-wins path below,
|
||||
// unchanged.
|
||||
|
||||
@@ -29,28 +29,28 @@ test "conversions: classify covers the built-in coercion ladder" {
|
||||
const tt = &module.types;
|
||||
|
||||
// no-op + Any box/unbox.
|
||||
try std.testing.expectEqual(Plan.no_op, cr.classify(.s64, .s64));
|
||||
try std.testing.expectEqual(Plan.unbox_any, cr.classify(.any, .s64));
|
||||
try std.testing.expectEqual(Plan.box_any, cr.classify(.s64, .any));
|
||||
try std.testing.expectEqual(Plan.no_op, cr.classify(.i64, .i64));
|
||||
try std.testing.expectEqual(Plan.unbox_any, cr.classify(.any, .i64));
|
||||
try std.testing.expectEqual(Plan.box_any, cr.classify(.i64, .any));
|
||||
|
||||
// Numeric / pointer ladder.
|
||||
try std.testing.expectEqual(Plan.widen, cr.classify(.s32, .s64));
|
||||
try std.testing.expectEqual(Plan.narrow, cr.classify(.s64, .s32));
|
||||
try std.testing.expectEqual(Plan.int_to_float, cr.classify(.s32, .f64));
|
||||
try std.testing.expectEqual(Plan.float_to_int, cr.classify(.f64, .s32));
|
||||
const ptr_s64 = tt.ptrTo(.s64);
|
||||
try std.testing.expectEqual(Plan.ptr_int_bitcast, cr.classify(ptr_s64, .s64));
|
||||
try std.testing.expectEqual(Plan.ptr_int_bitcast, cr.classify(.s64, ptr_s64));
|
||||
try std.testing.expectEqual(Plan.widen, cr.classify(.i32, .i64));
|
||||
try std.testing.expectEqual(Plan.narrow, cr.classify(.i64, .i32));
|
||||
try std.testing.expectEqual(Plan.int_to_float, cr.classify(.i32, .f64));
|
||||
try std.testing.expectEqual(Plan.float_to_int, cr.classify(.f64, .i32));
|
||||
const ptr_i64 = tt.ptrTo(.i64);
|
||||
try std.testing.expectEqual(Plan.ptr_int_bitcast, cr.classify(ptr_i64, .i64));
|
||||
try std.testing.expectEqual(Plan.ptr_int_bitcast, cr.classify(.i64, ptr_i64));
|
||||
|
||||
// Optional wrap / unwrap, and void → optional.
|
||||
const opt_s64 = tt.optionalOf(.s64);
|
||||
try std.testing.expectEqual(Plan.optional_wrap, cr.classify(.s64, opt_s64));
|
||||
try std.testing.expectEqual(Plan.optional_unwrap, cr.classify(opt_s64, .s64));
|
||||
try std.testing.expectEqual(Plan.void_to_optional, cr.classify(.void, opt_s64));
|
||||
const opt_i64 = tt.optionalOf(.i64);
|
||||
try std.testing.expectEqual(Plan.optional_wrap, cr.classify(.i64, opt_i64));
|
||||
try std.testing.expectEqual(Plan.optional_unwrap, cr.classify(opt_i64, .i64));
|
||||
try std.testing.expectEqual(Plan.void_to_optional, cr.classify(.void, opt_i64));
|
||||
|
||||
// Tuple → tuple, same arity.
|
||||
const t_ss = tt.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .s64, .s64 }, .names = null } });
|
||||
const t_ii = tt.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .s32, .s32 }, .names = null } });
|
||||
const t_ss = tt.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .i64, .i64 }, .names = null } });
|
||||
const t_ii = tt.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .i32, .i32 }, .names = null } });
|
||||
try std.testing.expectEqual(Plan.tuple_elementwise, cr.classify(t_ss, t_ii));
|
||||
|
||||
// Closure value → bare fn-ptr: rejected.
|
||||
@@ -100,17 +100,17 @@ test "conversions: classifyXX picks the xx-operator head decision" {
|
||||
const drawable = tt.findByName(tt.internString("Drawable")).?;
|
||||
|
||||
// Any source unboxes regardless of dst.
|
||||
try std.testing.expectEqual(XXPlan.unbox_any, cr.classifyXX(.any, .s64));
|
||||
try std.testing.expectEqual(XXPlan.unbox_any, cr.classifyXX(.any, .i64));
|
||||
// Same type → no-op.
|
||||
try std.testing.expectEqual(XXPlan.no_op, cr.classifyXX(.s64, .s64));
|
||||
try std.testing.expectEqual(XXPlan.no_op, cr.classifyXX(.i64, .i64));
|
||||
// dst is a protocol → erasure (checked before the src-protocol case).
|
||||
try std.testing.expectEqual(XXPlan.erase_protocol, cr.classifyXX(.s64, drawable));
|
||||
try std.testing.expectEqual(XXPlan.erase_protocol, cr.classifyXX(.i64, drawable));
|
||||
// src is a protocol, dst is a pointer → recover the ctx pointer.
|
||||
try std.testing.expectEqual(XXPlan.protocol_to_pointer, cr.classifyXX(drawable, tt.ptrTo(.s64)));
|
||||
try std.testing.expectEqual(XXPlan.protocol_to_pointer, cr.classifyXX(drawable, tt.ptrTo(.i64)));
|
||||
// src is a protocol but dst is NOT a pointer → fall to the ladder.
|
||||
try std.testing.expectEqual(XXPlan.coerce, cr.classifyXX(drawable, .s64));
|
||||
try std.testing.expectEqual(XXPlan.coerce, cr.classifyXX(drawable, .i64));
|
||||
// Pointer materialization (`xx value` into a `*T` slot, no built-in) defers
|
||||
// to the ladder + the user-`Into` pointer fallback in lowerXX.
|
||||
const a = tt.intern(.{ .@"struct" = .{ .name = tt.internString("A"), .fields = &.{} } });
|
||||
try std.testing.expectEqual(XXPlan.coerce, cr.classifyXX(a, tt.ptrTo(.s32)));
|
||||
try std.testing.expectEqual(XXPlan.coerce, cr.classifyXX(a, tt.ptrTo(.i32)));
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@ test "emit: main() returns 42" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func main() -> s64 { return 42; }
|
||||
_ = b.beginFunction(str(&module, "main"), &.{}, .s64);
|
||||
// func main() -> i64 { return 42; }
|
||||
_ = b.beginFunction(str(&module, "main"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
const c42 = b.constInt(42, .s64);
|
||||
b.ret(c42, .s64);
|
||||
const c42 = b.constInt(42, .i64);
|
||||
b.ret(c42, .i64);
|
||||
b.finalize();
|
||||
|
||||
// Emit to LLVM
|
||||
@@ -49,7 +49,7 @@ test "emit: main() returns 42" {
|
||||
const ir_str = emitter.dumpToString();
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "define") != null);
|
||||
// `main` is emitted with the C entry-point convention: it returns i32, so
|
||||
// the s64 const 42 is truncated to `ret i32 42`.
|
||||
// the i64 const 42 is truncated to `ret i32 42`.
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i32 42") != null);
|
||||
}
|
||||
|
||||
@@ -60,12 +60,12 @@ test "emit: add(a, b) returns a + b" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func add(a: s64, b: s64) -> s64 { return a + b; }
|
||||
// func add(a: i64, b: i64) -> i64 { return a + b; }
|
||||
const params = &[_]Function.Param{
|
||||
.{ .name = str(&module, "a"), .ty = .s64 },
|
||||
.{ .name = str(&module, "b"), .ty = .s64 },
|
||||
.{ .name = str(&module, "a"), .ty = .i64 },
|
||||
.{ .name = str(&module, "b"), .ty = .i64 },
|
||||
};
|
||||
_ = b.beginFunction(str(&module, "add"), params, .s64);
|
||||
_ = b.beginFunction(str(&module, "add"), params, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
@@ -78,10 +78,10 @@ test "emit: add(a, b) returns a + b" {
|
||||
// at 0, and params are accessed differently. The lowering pass emits
|
||||
// alloca+store for params. For this test, we use const_int to test
|
||||
// the add instruction directly.
|
||||
const a = b.constInt(10, .s64);
|
||||
const a_b = b.constInt(32, .s64);
|
||||
const sum = b.add(a, a_b, .s64);
|
||||
b.ret(sum, .s64);
|
||||
const a = b.constInt(10, .i64);
|
||||
const a_b = b.constInt(32, .i64);
|
||||
const sum = b.add(a, a_b, .i64);
|
||||
b.ret(sum, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_add", .{});
|
||||
@@ -138,14 +138,14 @@ test "emit: negation" {
|
||||
|
||||
// Negating a constant folds; negate a param so `sub 0, %x` is emitted.
|
||||
_ = b.beginFunction(str(&module, "negate"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "x"), .ty = .s64 },
|
||||
}, .s64);
|
||||
.{ .name = str(&module, "x"), .ty = .i64 },
|
||||
}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const val = Ref.fromIndex(0);
|
||||
const neg = b.emit(.{ .neg = .{ .operand = val } }, .s64);
|
||||
b.ret(neg, .s64);
|
||||
const neg = b.emit(.{ .neg = .{ .operand = val } }, .i64);
|
||||
b.ret(neg, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_neg", .{});
|
||||
@@ -189,16 +189,16 @@ test "emit: alloca, store, load" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> s64 { var x: s64 = 10; return x; }
|
||||
_ = b.beginFunction(str(&module, "f"), &.{}, .s64);
|
||||
// func f() -> i64 { var x: i64 = 10; return x; }
|
||||
_ = b.beginFunction(str(&module, "f"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const x_ptr = b.alloca(.s64); // alloca s64 → *s64
|
||||
const ten = b.constInt(10, .s64);
|
||||
const x_ptr = b.alloca(.i64); // alloca i64 → *i64
|
||||
const ten = b.constInt(10, .i64);
|
||||
b.store(x_ptr, ten); // store 10 → *x
|
||||
const loaded = b.load(x_ptr, .s64); // load *x → s64
|
||||
b.ret(loaded, .s64);
|
||||
const loaded = b.load(x_ptr, .i64); // load *x → i64
|
||||
b.ret(loaded, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_mem", .{});
|
||||
@@ -221,12 +221,12 @@ test "emit: comparison and branch" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f(a, b) -> s64 { if (a < b) return 1; else return 0; }
|
||||
// func f(a, b) -> i64 { if (a < b) return 1; else return 0; }
|
||||
// Params (not constants) so the icmp isn't folded.
|
||||
_ = b.beginFunction(str(&module, "cmpfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "a"), .ty = .s64 },
|
||||
.{ .name = str(&module, "b"), .ty = .s64 },
|
||||
}, .s64);
|
||||
.{ .name = str(&module, "a"), .ty = .i64 },
|
||||
.{ .name = str(&module, "b"), .ty = .i64 },
|
||||
}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
const then_bb = b.appendBlock(str(&module, "then"), &.{});
|
||||
const else_bb = b.appendBlock(str(&module, "else"), &.{});
|
||||
@@ -238,12 +238,12 @@ test "emit: comparison and branch" {
|
||||
b.condBr(cond, then_bb, &.{}, else_bb, &.{});
|
||||
|
||||
b.switchToBlock(then_bb);
|
||||
const one = b.constInt(1, .s64);
|
||||
b.ret(one, .s64);
|
||||
const one = b.constInt(1, .i64);
|
||||
b.ret(one, .i64);
|
||||
|
||||
b.switchToBlock(else_bb);
|
||||
const zero = b.constInt(0, .s64);
|
||||
b.ret(zero, .s64);
|
||||
const zero = b.constInt(0, .i64);
|
||||
b.ret(zero, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_cmp", .{});
|
||||
@@ -264,27 +264,27 @@ test "emit: function call" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func add(a: s64, b: s64) -> s64 { return a + b; } (using constants)
|
||||
// func add(a: i64, b: i64) -> i64 { return a + b; } (using constants)
|
||||
const add_id = b.beginFunction(str(&module, "addfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "a"), .ty = .s64 },
|
||||
.{ .name = str(&module, "b"), .ty = .s64 },
|
||||
}, .s64);
|
||||
.{ .name = str(&module, "a"), .ty = .i64 },
|
||||
.{ .name = str(&module, "b"), .ty = .i64 },
|
||||
}, .i64);
|
||||
const add_entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(add_entry);
|
||||
const p0 = b.constInt(0, .s64); // placeholder
|
||||
const p1 = b.constInt(0, .s64);
|
||||
const sum = b.add(p0, p1, .s64);
|
||||
b.ret(sum, .s64);
|
||||
const p0 = b.constInt(0, .i64); // placeholder
|
||||
const p1 = b.constInt(0, .i64);
|
||||
const sum = b.add(p0, p1, .i64);
|
||||
b.ret(sum, .i64);
|
||||
b.finalize();
|
||||
|
||||
// func main() -> s64 { return addfn(3, 4); }
|
||||
_ = b.beginFunction(str(&module, "main"), &.{}, .s64);
|
||||
// func main() -> i64 { return addfn(3, 4); }
|
||||
_ = b.beginFunction(str(&module, "main"), &.{}, .i64);
|
||||
const main_entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(main_entry);
|
||||
const three = b.constInt(3, .s64);
|
||||
const four = b.constInt(4, .s64);
|
||||
const result = b.call(add_id, &.{ three, four }, .s64);
|
||||
b.ret(result, .s64);
|
||||
const three = b.constInt(3, .i64);
|
||||
const four = b.constInt(4, .i64);
|
||||
const result = b.call(add_id, &.{ three, four }, .i64);
|
||||
b.ret(result, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_call", .{});
|
||||
@@ -298,7 +298,7 @@ test "emit: function call" {
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "addfn") != null);
|
||||
}
|
||||
|
||||
test "emit: widen conversion s32 to s64" {
|
||||
test "emit: widen conversion i32 to i64" {
|
||||
const alloc = std.testing.allocator;
|
||||
var module = Module.init(alloc);
|
||||
defer module.deinit();
|
||||
@@ -307,14 +307,14 @@ test "emit: widen conversion s32 to s64" {
|
||||
|
||||
// sext of a constant folds; widen a param so `sext` is emitted.
|
||||
_ = b.beginFunction(str(&module, "wfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "x"), .ty = .s32 },
|
||||
}, .s64);
|
||||
.{ .name = str(&module, "x"), .ty = .i32 },
|
||||
}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const val = Ref.fromIndex(0);
|
||||
const wide = b.widen(val, .s32, .s64);
|
||||
b.ret(wide, .s64);
|
||||
const wide = b.widen(val, .i32, .i64);
|
||||
b.ret(wide, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_widen", .{});
|
||||
@@ -338,10 +338,10 @@ test "emit: type conversion toLLVMType" {
|
||||
// Just verify toLLVMType doesn't crash for all builtin types
|
||||
_ = emitter.toLLVMType(.void);
|
||||
_ = emitter.toLLVMType(.bool);
|
||||
_ = emitter.toLLVMType(.s8);
|
||||
_ = emitter.toLLVMType(.s16);
|
||||
_ = emitter.toLLVMType(.s32);
|
||||
_ = emitter.toLLVMType(.s64);
|
||||
_ = emitter.toLLVMType(.i8);
|
||||
_ = emitter.toLLVMType(.i16);
|
||||
_ = emitter.toLLVMType(.i32);
|
||||
_ = emitter.toLLVMType(.i64);
|
||||
_ = emitter.toLLVMType(.u8);
|
||||
_ = emitter.toLLVMType(.u16);
|
||||
_ = emitter.toLLVMType(.u32);
|
||||
@@ -381,12 +381,12 @@ test "emit: abiCoerceParamType coerces C-ABI structs by size bucket" {
|
||||
defer module.deinit();
|
||||
|
||||
// Intern the shapes before building the emitter (toLLVMType reads live).
|
||||
const small = internStruct(&module, "Small", &.{ .s32, .s32 }); // 8 bytes
|
||||
const mid = internStruct(&module, "Mid", &.{ .s64, .s64 }); // 16 bytes
|
||||
const big = internStruct(&module, "Big", &.{ .s64, .s64, .s64 }); // 24 bytes
|
||||
const small = internStruct(&module, "Small", &.{ .i32, .i32 }); // 8 bytes
|
||||
const mid = internStruct(&module, "Mid", &.{ .i64, .i64 }); // 16 bytes
|
||||
const big = internStruct(&module, "Big", &.{ .i64, .i64, .i64 }); // 24 bytes
|
||||
const hfa_f = internStruct(&module, "HfaF", &.{ .f32, .f32, .f32, .f32 }); // 16, all-float
|
||||
const hfa_d = internStruct(&module, "HfaD", &.{ .f64, .f64 }); // 16, all-double
|
||||
const sl = module.types.sliceOf(.s32);
|
||||
const sl = module.types.sliceOf(.i32);
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_abi", .{});
|
||||
defer emitter.deinit();
|
||||
@@ -404,7 +404,7 @@ test "emit: abiCoerceParamType coerces C-ABI structs by size bucket" {
|
||||
try std.testing.expect(emitter.abiCoerceParamType(.string, emitter.toLLVMType(.string)) == emitter.cached_ptr);
|
||||
try std.testing.expect(emitter.abiCoerceParamType(sl, emitter.toLLVMType(sl)) == emitter.cached_ptr);
|
||||
// Scalars pass through unchanged.
|
||||
try std.testing.expect(emitter.abiCoerceParamType(.s32, emitter.toLLVMType(.s32)) == emitter.toLLVMType(.s32));
|
||||
try std.testing.expect(emitter.abiCoerceParamType(.i32, emitter.toLLVMType(.i32)) == emitter.toLLVMType(.i32));
|
||||
}
|
||||
|
||||
test "emit: needsByval only for > 16-byte non-HFA structs" {
|
||||
@@ -412,11 +412,11 @@ test "emit: needsByval only for > 16-byte non-HFA structs" {
|
||||
var module = Module.init(alloc);
|
||||
defer module.deinit();
|
||||
|
||||
const small = internStruct(&module, "Small", &.{ .s32, .s32 });
|
||||
const mid = internStruct(&module, "Mid", &.{ .s64, .s64 });
|
||||
const big = internStruct(&module, "Big", &.{ .s64, .s64, .s64 });
|
||||
const small = internStruct(&module, "Small", &.{ .i32, .i32 });
|
||||
const mid = internStruct(&module, "Mid", &.{ .i64, .i64 });
|
||||
const big = internStruct(&module, "Big", &.{ .i64, .i64, .i64 });
|
||||
const hfa_d = internStruct(&module, "HfaD", &.{ .f64, .f64 });
|
||||
const sl = module.types.sliceOf(.s32);
|
||||
const sl = module.types.sliceOf(.i32);
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_byval", .{});
|
||||
defer emitter.deinit();
|
||||
@@ -427,7 +427,7 @@ test "emit: needsByval only for > 16-byte non-HFA structs" {
|
||||
try std.testing.expect(!emitter.needsByval(hfa_d, emitter.toLLVMType(hfa_d))); // HFA
|
||||
try std.testing.expect(!emitter.needsByval(.string, emitter.toLLVMType(.string)));
|
||||
try std.testing.expect(!emitter.needsByval(sl, emitter.toLLVMType(sl)));
|
||||
try std.testing.expect(!emitter.needsByval(.s32, emitter.toLLVMType(.s32))); // non-struct
|
||||
try std.testing.expect(!emitter.needsByval(.i32, emitter.toLLVMType(.i32))); // non-struct
|
||||
}
|
||||
|
||||
// ── Struct/Enum/Union tests ─────────────────────────────────────────
|
||||
@@ -437,10 +437,10 @@ test "emit: struct_init and struct_get" {
|
||||
var module = Module.init(alloc);
|
||||
defer module.deinit();
|
||||
|
||||
// Create a struct type: Point { x: s64, y: s64 }
|
||||
// Create a struct type: Point { x: i64, y: i64 }
|
||||
const fields = &[_]types.TypeInfo.StructInfo.Field{
|
||||
.{ .name = str(&module, "x"), .ty = .s64 },
|
||||
.{ .name = str(&module, "y"), .ty = .s64 },
|
||||
.{ .name = str(&module, "x"), .ty = .i64 },
|
||||
.{ .name = str(&module, "y"), .ty = .i64 },
|
||||
};
|
||||
const owned_fields = alloc.dupe(types.TypeInfo.StructInfo.Field, fields) catch unreachable;
|
||||
defer alloc.free(owned_fields);
|
||||
@@ -451,20 +451,20 @@ test "emit: struct_init and struct_get" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f(v) -> s64 { p = Point{v, 20}; return p.y; }
|
||||
// func f(v) -> i64 { p = Point{v, 20}; return p.y; }
|
||||
// A param operand keeps the aggregate non-constant so insertvalue /
|
||||
// extractvalue survive (a fully-constant struct would be folded).
|
||||
_ = b.beginFunction(str(&module, "f"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "v"), .ty = .s64 },
|
||||
}, .s64);
|
||||
.{ .name = str(&module, "v"), .ty = .i64 },
|
||||
}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const x = Ref.fromIndex(0);
|
||||
const y = b.constInt(20, .s64);
|
||||
const y = b.constInt(20, .i64);
|
||||
const p = b.structInit(&.{ x, y }, point_ty);
|
||||
const py = b.structGet(p, 1, .s64);
|
||||
b.ret(py, .s64);
|
||||
const py = b.structGet(p, 1, .i64);
|
||||
b.ret(py, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_struct", .{});
|
||||
@@ -486,8 +486,8 @@ test "emit: struct_gep (pointer to field)" {
|
||||
|
||||
// Create struct type
|
||||
const fields = &[_]types.TypeInfo.StructInfo.Field{
|
||||
.{ .name = str(&module, "x"), .ty = .s64 },
|
||||
.{ .name = str(&module, "y"), .ty = .s64 },
|
||||
.{ .name = str(&module, "x"), .ty = .i64 },
|
||||
.{ .name = str(&module, "y"), .ty = .i64 },
|
||||
};
|
||||
const owned_fields = alloc.dupe(types.TypeInfo.StructInfo.Field, fields) catch unreachable;
|
||||
defer alloc.free(owned_fields);
|
||||
@@ -495,21 +495,21 @@ test "emit: struct_gep (pointer to field)" {
|
||||
.name = str(&module, "Point"),
|
||||
.fields = owned_fields,
|
||||
} });
|
||||
const ptr_s64 = module.types.ptrTo(.s64);
|
||||
const ptr_i64 = module.types.ptrTo(.i64);
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> s64 { var p: Point; p.y = 42; return p.y; }
|
||||
_ = b.beginFunction(str(&module, "gepfn"), &.{}, .s64);
|
||||
// func f() -> i64 { var p: Point; p.y = 42; return p.y; }
|
||||
_ = b.beginFunction(str(&module, "gepfn"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const p_ptr = b.alloca(point_ty);
|
||||
const y_ptr = b.structGepTyped(p_ptr, 1, ptr_s64, point_ty);
|
||||
const c42 = b.constInt(42, .s64);
|
||||
const y_ptr = b.structGepTyped(p_ptr, 1, ptr_i64, point_ty);
|
||||
const c42 = b.constInt(42, .i64);
|
||||
b.store(y_ptr, c42);
|
||||
const loaded = b.load(y_ptr, .s64);
|
||||
b.ret(loaded, .s64);
|
||||
const loaded = b.load(y_ptr, .i64);
|
||||
b.ret(loaded, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_gep", .{});
|
||||
@@ -544,16 +544,16 @@ test "emit: enum_init and enum_tag (plain enum)" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> s64 { c = Color.Green; return tag(c); }
|
||||
_ = b.beginFunction(str(&module, "enumfn"), &.{}, .s64);
|
||||
// func f() -> i64 { c = Color.Green; return tag(c); }
|
||||
_ = b.beginFunction(str(&module, "enumfn"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const green = b.enumInit(1, Ref.none, color_ty); // Green = tag 1
|
||||
const tag = b.enumTag(green, .s32);
|
||||
// Widen tag from s32 to s64 for the return
|
||||
const wide = b.widen(tag, .s32, .s64);
|
||||
b.ret(wide, .s64);
|
||||
const tag = b.enumTag(green, .i32);
|
||||
// Widen tag from i32 to i64 for the return
|
||||
const wide = b.widen(tag, .i32, .i64);
|
||||
b.ret(wide, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_enum", .{});
|
||||
@@ -572,17 +572,17 @@ test "emit: tagged union (enum_init with payload, enum_tag, enum_payload)" {
|
||||
var module = Module.init(alloc);
|
||||
defer module.deinit();
|
||||
|
||||
// Create a tagged union: Shape { Circle: f64, Rect: s64 }
|
||||
// Create a tagged union: Shape { Circle: f64, Rect: i64 }
|
||||
const ufields = &[_]types.TypeInfo.StructInfo.Field{
|
||||
.{ .name = str(&module, "Circle"), .ty = .f64 },
|
||||
.{ .name = str(&module, "Rect"), .ty = .s64 },
|
||||
.{ .name = str(&module, "Rect"), .ty = .i64 },
|
||||
};
|
||||
const owned_ufields = alloc.dupe(types.TypeInfo.StructInfo.Field, ufields) catch unreachable;
|
||||
defer alloc.free(owned_ufields);
|
||||
const shape_ty = module.types.intern(.{ .tagged_union = .{
|
||||
.name = str(&module, "Shape"),
|
||||
.fields = owned_ufields,
|
||||
.tag_type = .s64,
|
||||
.tag_type = .i64,
|
||||
} });
|
||||
|
||||
var b = Builder.init(&module);
|
||||
@@ -597,7 +597,7 @@ test "emit: tagged union (enum_init with payload, enum_tag, enum_payload)" {
|
||||
|
||||
const radius = Ref.fromIndex(0);
|
||||
const shape = b.enumInit(0, radius, shape_ty); // Circle = tag 0
|
||||
const tag = b.emit(.{ .enum_tag = .{ .operand = shape } }, .s64);
|
||||
const tag = b.emit(.{ .enum_tag = .{ .operand = shape } }, .i64);
|
||||
_ = tag; // tag is used but we just check it doesn't crash
|
||||
const payload = b.emit(.{ .enum_payload = .{ .base = shape, .field_index = 0 } }, .f64);
|
||||
b.ret(payload, .f64);
|
||||
@@ -623,9 +623,9 @@ test "emit: union_get (reinterpret union field)" {
|
||||
var module = Module.init(alloc);
|
||||
defer module.deinit();
|
||||
|
||||
// Untagged union: Data { as_int: s64, as_float: f64 }
|
||||
// Untagged union: Data { as_int: i64, as_float: f64 }
|
||||
const ufields = &[_]types.TypeInfo.StructInfo.Field{
|
||||
.{ .name = str(&module, "as_int"), .ty = .s64 },
|
||||
.{ .name = str(&module, "as_int"), .ty = .i64 },
|
||||
.{ .name = str(&module, "as_float"), .ty = .f64 },
|
||||
};
|
||||
const owned_ufields = alloc.dupe(types.TypeInfo.StructInfo.Field, ufields) catch unreachable;
|
||||
@@ -637,15 +637,15 @@ test "emit: union_get (reinterpret union field)" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> s64 { d = Data.as_int(42); return union_get(d, 0) as s64; }
|
||||
_ = b.beginFunction(str(&module, "ugfn"), &.{}, .s64);
|
||||
// func f() -> i64 { d = Data.as_int(42); return union_get(d, 0) as i64; }
|
||||
_ = b.beginFunction(str(&module, "ugfn"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const val = b.constInt(42, .s64);
|
||||
const val = b.constInt(42, .i64);
|
||||
const d = b.enumInit(0, val, data_ty);
|
||||
const got = b.emit(.{ .union_get = .{ .base = d, .field_index = 0 } }, .s64);
|
||||
b.ret(got, .s64);
|
||||
const got = b.emit(.{ .union_get = .{ .base = d, .field_index = 0 } }, .i64);
|
||||
b.ret(got, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_union_get", .{});
|
||||
@@ -667,19 +667,19 @@ test "emit: array index_get" {
|
||||
var module = Module.init(alloc);
|
||||
defer module.deinit();
|
||||
|
||||
const arr_ty = module.types.arrayOf(.s64, 3);
|
||||
const arr_ty = module.types.arrayOf(.i64, 3);
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> s64 { arr: [3]s64 = ---; return arr[1]; }
|
||||
_ = b.beginFunction(str(&module, "arr_idx"), &.{}, .s64);
|
||||
// func f() -> i64 { arr: [3]i64 = ---; return arr[1]; }
|
||||
_ = b.beginFunction(str(&module, "arr_idx"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const undef_arr = b.emit(.{ .const_undef = {} }, arr_ty);
|
||||
const idx = b.constInt(1, .s64);
|
||||
const elem = b.emit(.{ .index_get = .{ .lhs = undef_arr, .rhs = idx } }, .s64);
|
||||
b.ret(elem, .s64);
|
||||
const idx = b.constInt(1, .i64);
|
||||
const elem = b.emit(.{ .index_get = .{ .lhs = undef_arr, .rhs = idx } }, .i64);
|
||||
b.ret(elem, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_arr_idx", .{});
|
||||
@@ -700,17 +700,17 @@ test "emit: length on slice" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f(s: string) -> s64 { return s.len; }
|
||||
// func f(s: string) -> i64 { return s.len; }
|
||||
// A string param keeps the value non-constant so extractvalue survives.
|
||||
_ = b.beginFunction(str(&module, "strlen"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "s"), .ty = .string },
|
||||
}, .s64);
|
||||
}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const s = Ref.fromIndex(0);
|
||||
const len = b.emit(.{ .length = .{ .operand = s } }, .s64);
|
||||
b.ret(len, .s64);
|
||||
const len = b.emit(.{ .length = .{ .operand = s } }, .i64);
|
||||
b.ret(len, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_len", .{});
|
||||
@@ -762,12 +762,12 @@ test "emit: array_to_slice" {
|
||||
var module = Module.init(alloc);
|
||||
defer module.deinit();
|
||||
|
||||
const arr_ty = module.types.arrayOf(.s64, 4);
|
||||
const slice_ty = module.types.sliceOf(.s64);
|
||||
const arr_ty = module.types.arrayOf(.i64, 4);
|
||||
const slice_ty = module.types.sliceOf(.i64);
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> []s64 { var arr: [4]s64 = ---; return arr[:]; }
|
||||
// func f() -> []i64 { var arr: [4]i64 = ---; return arr[:]; }
|
||||
_ = b.beginFunction(str(&module, "a2s"), &.{}, slice_ty);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
@@ -798,13 +798,13 @@ test "emit: subslice" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f(s: []u8, lo: s64, hi: s64) -> []u8 { return s[lo..hi]; }
|
||||
// func f(s: []u8, lo: i64, hi: i64) -> []u8 { return s[lo..hi]; }
|
||||
// All operands are params: a constant base folds the GEP, and constant
|
||||
// lo/hi fold the `hi - lo` subtraction.
|
||||
_ = b.beginFunction(str(&module, "ssfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "s"), .ty = slice_ty },
|
||||
.{ .name = str(&module, "lo"), .ty = .s64 },
|
||||
.{ .name = str(&module, "hi"), .ty = .s64 },
|
||||
.{ .name = str(&module, "lo"), .ty = .i64 },
|
||||
.{ .name = str(&module, "hi"), .ty = .i64 },
|
||||
}, slice_ty);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
@@ -836,22 +836,22 @@ test "emit: optional_wrap and optional_unwrap (value type)" {
|
||||
var module = Module.init(alloc);
|
||||
defer module.deinit();
|
||||
|
||||
const opt_ty = module.types.optionalOf(.s64);
|
||||
const opt_ty = module.types.optionalOf(.i64);
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f(v) -> s64 { opt = wrap(v); return unwrap(opt); }
|
||||
// func f(v) -> i64 { opt = wrap(v); return unwrap(opt); }
|
||||
// Param value keeps the optional non-constant (else insertvalue folds).
|
||||
_ = b.beginFunction(str(&module, "optfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "v"), .ty = .s64 },
|
||||
}, .s64);
|
||||
.{ .name = str(&module, "v"), .ty = .i64 },
|
||||
}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const val = Ref.fromIndex(0);
|
||||
const wrapped = b.optionalWrap(val, opt_ty);
|
||||
const unwrapped = b.optionalUnwrap(wrapped, .s64);
|
||||
b.ret(unwrapped, .s64);
|
||||
const unwrapped = b.optionalUnwrap(wrapped, .i64);
|
||||
b.ret(unwrapped, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_opt", .{});
|
||||
@@ -872,13 +872,13 @@ test "emit: optional_has_value" {
|
||||
var module = Module.init(alloc);
|
||||
defer module.deinit();
|
||||
|
||||
const opt_ty = module.types.optionalOf(.s64);
|
||||
const opt_ty = module.types.optionalOf(.i64);
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// Param value keeps the optional non-constant (else extractvalue folds).
|
||||
_ = b.beginFunction(str(&module, "hasfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "v"), .ty = .s64 },
|
||||
.{ .name = str(&module, "v"), .ty = .i64 },
|
||||
}, .bool);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
@@ -909,15 +909,15 @@ test "emit: switch_br" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f(x: s64) -> s64 { match x { 0 => 10, 1 => 20, _ => 30 } }
|
||||
_ = b.beginFunction(str(&module, "swfn"), &.{}, .s64);
|
||||
// func f(x: i64) -> i64 { match x { 0 => 10, 1 => 20, _ => 30 } }
|
||||
_ = b.beginFunction(str(&module, "swfn"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
const case0 = b.appendBlock(str(&module, "case0"), &.{});
|
||||
const case1 = b.appendBlock(str(&module, "case1"), &.{});
|
||||
const default_bb = b.appendBlock(str(&module, "default"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const x = b.constInt(1, .s64);
|
||||
const x = b.constInt(1, .i64);
|
||||
const cases = alloc.dupe(inst_mod.SwitchBranch.Case, &.{
|
||||
.{ .value = 0, .target = case0, .args = &.{} },
|
||||
.{ .value = 1, .target = case1, .args = &.{} },
|
||||
@@ -931,13 +931,13 @@ test "emit: switch_br" {
|
||||
} }, .void);
|
||||
|
||||
b.switchToBlock(case0);
|
||||
b.ret(b.constInt(10, .s64), .s64);
|
||||
b.ret(b.constInt(10, .i64), .i64);
|
||||
|
||||
b.switchToBlock(case1);
|
||||
b.ret(b.constInt(20, .s64), .s64);
|
||||
b.ret(b.constInt(20, .i64), .i64);
|
||||
|
||||
b.switchToBlock(default_bb);
|
||||
b.ret(b.constInt(30, .s64), .s64);
|
||||
b.ret(b.constInt(30, .i64), .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_switch", .{});
|
||||
@@ -957,18 +957,18 @@ test "emit: closure_create" {
|
||||
var module = Module.init(alloc);
|
||||
defer module.deinit();
|
||||
|
||||
const closure_ty = module.types.closureType(&.{.s64}, .s64);
|
||||
const closure_ty = module.types.closureType(&.{.i64}, .i64);
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// Create a dummy trampoline function
|
||||
const tramp_id = b.beginFunction(str(&module, "tramp"), &[_]inst_mod.Function.Param{
|
||||
.{ .name = str(&module, "env"), .ty = .s64 },
|
||||
.{ .name = str(&module, "x"), .ty = .s64 },
|
||||
}, .s64);
|
||||
.{ .name = str(&module, "env"), .ty = .i64 },
|
||||
.{ .name = str(&module, "x"), .ty = .i64 },
|
||||
}, .i64);
|
||||
const tramp_entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(tramp_entry);
|
||||
b.ret(b.constInt(0, .s64), .s64);
|
||||
b.ret(b.constInt(0, .i64), .i64);
|
||||
b.finalize();
|
||||
|
||||
// func f(e: *void) -> closure { return closure_create(tramp, e); }
|
||||
@@ -1004,18 +1004,18 @@ test "emit: box_any and unbox_any" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f(v) -> s64 { a = box(v); return unbox(a); }
|
||||
// func f(v) -> i64 { a = box(v); return unbox(a); }
|
||||
// Param value keeps the boxed Any non-constant (else insertvalue folds).
|
||||
_ = b.beginFunction(str(&module, "anyfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "v"), .ty = .s64 },
|
||||
}, .s64);
|
||||
.{ .name = str(&module, "v"), .ty = .i64 },
|
||||
}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const val = Ref.fromIndex(0);
|
||||
const boxed = b.emit(.{ .box_any = .{ .operand = val, .source_type = .s64 } }, .any);
|
||||
const unboxed = b.emit(.{ .unbox_any = .{ .operand = boxed } }, .s64);
|
||||
b.ret(unboxed, .s64);
|
||||
const boxed = b.emit(.{ .box_any = .{ .operand = val, .source_type = .i64 } }, .any);
|
||||
const unboxed = b.emit(.{ .unbox_any = .{ .operand = boxed } }, .i64);
|
||||
b.ret(unboxed, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_any", .{});
|
||||
@@ -1036,15 +1036,15 @@ test "emit: ERR E3.0 — DWARF debug info (compile unit + subprogram + per-inst
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func main() -> s64 { return 42; } — with the `return` instruction
|
||||
// func main() -> i64 { return 42; } — with the `return` instruction
|
||||
// carrying a span that lands on line 3 of the source map below.
|
||||
_ = b.beginFunction(str(&module, "main"), &.{}, .s64);
|
||||
_ = b.beginFunction(str(&module, "main"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
// "a\nb\nXYZ" — byte offset 4 ('X') is line 3, col 1.
|
||||
b.current_span = .{ .start = 4, .end = 5 };
|
||||
const c42 = b.constInt(42, .s64);
|
||||
b.ret(c42, .s64);
|
||||
const c42 = b.constInt(42, .i64);
|
||||
b.ret(c42, .i64);
|
||||
b.finalize();
|
||||
|
||||
// Source map keyed on the main file. setDebugContext + opt none
|
||||
@@ -1080,10 +1080,10 @@ test "emit: ERR E3.0 — no DWARF without a debug context (unit-test default)" {
|
||||
defer module.deinit();
|
||||
|
||||
var b = Builder.init(&module);
|
||||
_ = b.beginFunction(str(&module, "main"), &.{}, .s64);
|
||||
_ = b.beginFunction(str(&module, "main"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
b.ret(b.constInt(42, .s64), .s64);
|
||||
b.ret(b.constInt(42, .i64), .i64);
|
||||
b.finalize();
|
||||
|
||||
// No setDebugContext call → no source map → debug info off even at
|
||||
@@ -1111,9 +1111,9 @@ test "emit: argIRTypeOrFail surfaces .unresolved for an unresolvable FFI arg ref
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func ffifn(a: s64, b: f64) -> void { <entry> }
|
||||
// func ffifn(a: i64, b: f64) -> void { <entry> }
|
||||
const fid = b.beginFunction(str(&module, "ffifn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "a"), .ty = .s64 },
|
||||
.{ .name = str(&module, "a"), .ty = .i64 },
|
||||
.{ .name = str(&module, "b"), .ty = .f64 },
|
||||
}, .void);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
@@ -1127,7 +1127,7 @@ test "emit: argIRTypeOrFail surfaces .unresolved for an unresolvable FFI arg ref
|
||||
|
||||
// Happy path: a real arg ref (param 0 / param 1) resolves byte-identically
|
||||
// to its declared IR type — the FFI fast path is unchanged.
|
||||
try std.testing.expectEqual(TypeId.s64, emitter.argIRTypeOrFail(Ref.fromIndex(0)));
|
||||
try std.testing.expectEqual(TypeId.i64, emitter.argIRTypeOrFail(Ref.fromIndex(0)));
|
||||
try std.testing.expectEqual(TypeId.f64, emitter.argIRTypeOrFail(Ref.fromIndex(1)));
|
||||
|
||||
// A ref past every param and instruction is unresolvable.
|
||||
@@ -1145,12 +1145,12 @@ test "emit: argIRTypeOrFail surfaces .unresolved for an unresolvable FFI arg ref
|
||||
try std.testing.expect(emitter.argIRTypeOrFail(bogus) != .void);
|
||||
}
|
||||
|
||||
// ── reflection-builtin arg-type lookup must fail loudly, never `.s64` ──
|
||||
// ── reflection-builtin arg-type lookup must fail loudly, never `.i64` ──
|
||||
// `reflectArgRepr` backs the `type_name` / `type_eq` reflection builtins, which read
|
||||
// their `Type` arg as a boxed `Any` aggregate (`.any` → extract value field) or a bare
|
||||
// i64 TypeId index. A ref it cannot resolve is a codegen invariant violation; it must
|
||||
// surface `.unresolved` (which the emit site hard-panics on) instead of the old silent
|
||||
// `getRefIRType(arg) orelse .s64` default that would mis-classify a boxed arg as bare
|
||||
// `getRefIRType(arg) orelse .i64` default that would mis-classify a boxed arg as bare
|
||||
// and read the wrong value with no diagnostic.
|
||||
test "emit: reflectArgRepr surfaces .unresolved for an unresolvable reflection arg ref (issue 0075)" {
|
||||
const alloc = std.testing.allocator;
|
||||
@@ -1159,10 +1159,10 @@ test "emit: reflectArgRepr surfaces .unresolved for an unresolvable reflection a
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func reflfn(boxed: any, bare: s64) -> void { <entry> }
|
||||
// func reflfn(boxed: any, bare: i64) -> void { <entry> }
|
||||
const fid = b.beginFunction(str(&module, "reflfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "boxed"), .ty = .any },
|
||||
.{ .name = str(&module, "bare"), .ty = .s64 },
|
||||
.{ .name = str(&module, "bare"), .ty = .i64 },
|
||||
}, .void);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
@@ -1174,7 +1174,7 @@ test "emit: reflectArgRepr surfaces .unresolved for an unresolvable reflection a
|
||||
emitter.current_func_idx = fid.index();
|
||||
|
||||
// Happy path: a boxed `.any` Type arg classifies as `.boxed` (extract value
|
||||
// field); a bare `.s64` TypeId arg classifies as `.bare` (use directly).
|
||||
// field); a bare `.i64` TypeId arg classifies as `.bare` (use directly).
|
||||
// These decisions are byte-identical to the pre-fix `== .any` gate.
|
||||
try std.testing.expectEqual(LLVMEmitter.ReflectArgRepr.boxed, emitter.reflectArgRepr(Ref.fromIndex(0)));
|
||||
try std.testing.expectEqual(LLVMEmitter.ReflectArgRepr.bare, emitter.reflectArgRepr(Ref.fromIndex(1)));
|
||||
@@ -1183,11 +1183,11 @@ test "emit: reflectArgRepr surfaces .unresolved for an unresolvable reflection a
|
||||
const bogus = Ref.fromIndex(100_000);
|
||||
try std.testing.expectEqual(@as(?TypeId, null), emitter.getRefIRType(bogus));
|
||||
|
||||
// Fail-before: the old `getRefIRType(arg) orelse .s64` would silently yield
|
||||
// `.s64` here — which `!= .any`, so the reflection arm would treat a failed
|
||||
// Fail-before: the old `getRefIRType(arg) orelse .i64` would silently yield
|
||||
// `.i64` here — which `!= .any`, so the reflection arm would treat a failed
|
||||
// lookup as a bare i64 and read the wrong value with no diagnostic.
|
||||
try std.testing.expectEqual(TypeId.s64, emitter.getRefIRType(bogus) orelse TypeId.s64);
|
||||
try std.testing.expect((emitter.getRefIRType(bogus) orelse TypeId.s64) != .any);
|
||||
try std.testing.expectEqual(TypeId.i64, emitter.getRefIRType(bogus) orelse TypeId.i64);
|
||||
try std.testing.expect((emitter.getRefIRType(bogus) orelse TypeId.i64) != .any);
|
||||
|
||||
// Pass-after: the classifier returns the dedicated `.unresolved` variant,
|
||||
// never `.bare`, so the emit site trips its hard panic instead of silently
|
||||
|
||||
@@ -948,7 +948,7 @@ pub const LLVMEmitter = struct {
|
||||
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
||||
.aggregate => |agg| self.emitConstAggregate(agg, llvm_ty, false),
|
||||
.vtable => c.LLVMConstNull(llvm_ty), // placeholder — initialized in initVtableGlobals after function declarations
|
||||
// A top-level null-pointer global (`p : *s64 = null;`) and a
|
||||
// A top-level null-pointer global (`p : *i64 = null;`) and a
|
||||
// zero-initialized global both emit as the all-zero constant
|
||||
// of the global's type.
|
||||
.null_val, .zeroinit => c.LLVMConstNull(llvm_ty),
|
||||
@@ -2164,7 +2164,7 @@ pub const LLVMEmitter = struct {
|
||||
if (kind == c.LLVMIntegerTypeKind) {
|
||||
const width = c.LLVMGetIntTypeWidth(ty);
|
||||
if (width < 64) {
|
||||
return c.LLVMBuildSExt(self.builder, val, self.cached_i64, "s64");
|
||||
return c.LLVMBuildSExt(self.builder, val, self.cached_i64, "i64");
|
||||
}
|
||||
return val;
|
||||
}
|
||||
@@ -2198,11 +2198,11 @@ pub const LLVMEmitter = struct {
|
||||
const info = self.ir_mod.types.get(ty);
|
||||
return switch (info) {
|
||||
.signed => |w| switch (w) {
|
||||
8 => TypeId.s8.index(),
|
||||
16 => TypeId.s16.index(),
|
||||
32 => TypeId.s32.index(),
|
||||
64 => TypeId.s64.index(),
|
||||
else => if (w <= 32) TypeId.s32.index() else TypeId.s64.index(),
|
||||
8 => TypeId.i8.index(),
|
||||
16 => TypeId.i16.index(),
|
||||
32 => TypeId.i32.index(),
|
||||
64 => TypeId.i64.index(),
|
||||
else => if (w <= 32) TypeId.i32.index() else TypeId.i64.index(),
|
||||
},
|
||||
.unsigned => |w| switch (w) {
|
||||
8 => TypeId.u8.index(),
|
||||
@@ -2278,7 +2278,7 @@ pub const LLVMEmitter = struct {
|
||||
if (val_w == 1) {
|
||||
return c.LLVMBuildUIToFP(self.builder, val, param_ty, "ca.uitofp");
|
||||
}
|
||||
// Default to SIToFP since most sx integers are signed (s64).
|
||||
// Default to SIToFP since most sx integers are signed (i64).
|
||||
// Explicit unsigned conversions go through the IR widen/narrow path.
|
||||
return c.LLVMBuildSIToFP(self.builder, val, param_ty, "ca.sitofp");
|
||||
}
|
||||
@@ -2386,7 +2386,7 @@ pub const LLVMEmitter = struct {
|
||||
/// Resolve the IR type of a foreign-call argument ref. Every FFI arg ref is
|
||||
/// a real function param or block instruction result, so a `null` here is a
|
||||
/// codegen invariant violation, not a recoverable case: return the dedicated
|
||||
/// `.unresolved` sentinel — never `.void`/`.s64` — so the failure cannot be
|
||||
/// `.unresolved` sentinel — never `.void`/`.i64` — so the failure cannot be
|
||||
/// mistaken for a real type and trips `toLLVMType`'s hard tripwire at the call
|
||||
/// site instead of silently emitting a void-typed foreign argument.
|
||||
pub fn argIRTypeOrFail(self: *LLVMEmitter, arg_ref: Ref) TypeId {
|
||||
@@ -2397,7 +2397,7 @@ pub const LLVMEmitter = struct {
|
||||
/// argument: boxed inside an `Any` aggregate (extract the value field) vs a
|
||||
/// bare i64 `TypeId` index. The IR-type lookup is must-succeed, so it routes
|
||||
/// through `argIRTypeOrFail`; a failed lookup surfaces as `.unresolved` —
|
||||
/// never a silent `.s64` that would mis-classify a boxed arg as bare and read
|
||||
/// never a silent `.i64` that would mis-classify a boxed arg as bare and read
|
||||
/// the wrong value. The caller turns `.unresolved` into a hard tripwire.
|
||||
pub const ReflectArgRepr = enum { boxed, bare, unresolved };
|
||||
|
||||
@@ -2938,7 +2938,7 @@ pub fn isFloatOrVecFloat(ty: TypeId, types: *const TypeTable) bool {
|
||||
|
||||
pub fn isSignedType(ty: TypeId) bool {
|
||||
return switch (ty) {
|
||||
.s8, .s16, .s32, .s64, .isize => true,
|
||||
.i8, .i16, .i32, .i64, .isize => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
@@ -2953,10 +2953,10 @@ fn floatBits(ty: TypeId) u32 {
|
||||
|
||||
fn intBits(ty: TypeId) u32 {
|
||||
return switch (ty) {
|
||||
.s8, .u8 => 8,
|
||||
.s16, .u16 => 16,
|
||||
.s32, .u32 => 32,
|
||||
.s64, .u64 => 64,
|
||||
.i8, .u8 => 8,
|
||||
.i16, .u16 => 16,
|
||||
.i32, .u32 => 32,
|
||||
.i64, .u64 => 64,
|
||||
.bool => 1,
|
||||
.usize, .isize => 0, // target-dependent — caller must query pointer_size
|
||||
else => 64,
|
||||
|
||||
@@ -28,7 +28,7 @@ test "expr_typer: literal shapes" {
|
||||
var bool_n = node(.{ .bool_literal = .{ .value = true } });
|
||||
var str_n = node(.{ .string_literal = .{ .raw = "hi" } });
|
||||
|
||||
try std.testing.expectEqual(TypeId.s64, l.inferExprType(&int_n));
|
||||
try std.testing.expectEqual(TypeId.i64, l.inferExprType(&int_n));
|
||||
try std.testing.expectEqual(TypeId.f64, l.inferExprType(&float_n));
|
||||
try std.testing.expectEqual(TypeId.bool, l.inferExprType(&bool_n));
|
||||
try std.testing.expectEqual(TypeId.string, l.inferExprType(&str_n));
|
||||
@@ -47,14 +47,14 @@ test "expr_typer: binary comparison is bool, int arithmetic stays int" {
|
||||
try std.testing.expectEqual(TypeId.bool, l.inferExprType(&cmp));
|
||||
|
||||
var add = node(.{ .binary_op = .{ .op = .add, .lhs = &lhs, .rhs = &rhs } });
|
||||
try std.testing.expectEqual(TypeId.s64, l.inferExprType(&add));
|
||||
try std.testing.expectEqual(TypeId.i64, l.inferExprType(&add));
|
||||
}
|
||||
|
||||
// A non-comparison binary op infers the PROMOTED result
|
||||
// of (lhs, rhs), not the LHS alone — so a mixed int+float op types as the float
|
||||
// in EITHER operand order (was LHS-biased: `int + float` → s64 while
|
||||
// in EITHER operand order (was LHS-biased: `int + float` → i64 while
|
||||
// `float + int` → f64). This is what feeds the typed-const validation that
|
||||
// rejected `s64 : 0.5 + M` but not `s64 : M + 0.5`.
|
||||
// rejected `i64 : 0.5 + M` but not `i64 : M + 0.5`.
|
||||
test "expr_typer: mixed int+float arithmetic promotes to float, order-independent" {
|
||||
const alloc = std.testing.allocator;
|
||||
var module = ir_mod.Module.init(alloc);
|
||||
@@ -64,7 +64,7 @@ test "expr_typer: mixed int+float arithmetic promotes to float, order-independen
|
||||
var int_n = node(.{ .int_literal = .{ .value = 2 } });
|
||||
var float_n = node(.{ .float_literal = .{ .value = 0.5 } });
|
||||
|
||||
// int LHS, float RHS → f64 (was s64 before the fix).
|
||||
// int LHS, float RHS → f64 (was i64 before the fix).
|
||||
var add_if = node(.{ .binary_op = .{ .op = .add, .lhs = &int_n, .rhs = &float_n } });
|
||||
try std.testing.expectEqual(TypeId.f64, l.inferExprType(&add_if));
|
||||
|
||||
@@ -81,12 +81,12 @@ test "expr_typer: mixed int+float arithmetic promotes to float, order-independen
|
||||
// `lowerBinaryOp`'s value type and `inferExprType`): an integer LHS with a
|
||||
// floating-point RHS promotes to the float; every other pairing keeps the LHS.
|
||||
test "arithResultType: int×float promotes to float, else takes lhs" {
|
||||
try std.testing.expectEqual(TypeId.f64, Lowering.arithResultType(.s64, .f64));
|
||||
try std.testing.expectEqual(TypeId.f64, Lowering.arithResultType(.i64, .f64));
|
||||
try std.testing.expectEqual(TypeId.f32, Lowering.arithResultType(.u32, .f32));
|
||||
try std.testing.expectEqual(TypeId.f32, Lowering.arithResultType(.s64, .f32));
|
||||
try std.testing.expectEqual(TypeId.f32, Lowering.arithResultType(.i64, .f32));
|
||||
// Non-promoting pairings keep the LHS type.
|
||||
try std.testing.expectEqual(TypeId.s64, Lowering.arithResultType(.s64, .s64));
|
||||
try std.testing.expectEqual(TypeId.f64, Lowering.arithResultType(.f64, .s64));
|
||||
try std.testing.expectEqual(TypeId.i64, Lowering.arithResultType(.i64, .i64));
|
||||
try std.testing.expectEqual(TypeId.f64, Lowering.arithResultType(.f64, .i64));
|
||||
try std.testing.expectEqual(TypeId.f32, Lowering.arithResultType(.f32, .f64));
|
||||
}
|
||||
|
||||
@@ -133,9 +133,9 @@ test "expr_typer: raw value binding shadows numeric-limit, bare type still folds
|
||||
defer module.deinit();
|
||||
var l = Lowering.init(&module);
|
||||
|
||||
// A struct `Box { epsilon: s64 }` bound to the raw name `` `f64 ``.
|
||||
// A struct `Box { epsilon: i64 }` bound to the raw name `` `f64 ``.
|
||||
const box_fields = [_]ir_mod.types.TypeInfo.StructInfo.Field{
|
||||
.{ .name = module.types.internString("epsilon"), .ty = .s64 },
|
||||
.{ .name = module.types.internString("epsilon"), .ty = .i64 },
|
||||
};
|
||||
const box_ty = module.types.intern(.{ .@"struct" = .{
|
||||
.name = module.types.internString("Box"),
|
||||
@@ -148,10 +148,10 @@ test "expr_typer: raw value binding shadows numeric-limit, bare type still folds
|
||||
scope.put("f64", .{ .ref = Ref.none, .ty = box_ty, .is_alloca = false });
|
||||
|
||||
// `` `f64.epsilon `` — identifier receiver resolving to the value binding →
|
||||
// ordinary field read, types as s64 (the field), not f64.
|
||||
// ordinary field read, types as i64 (the field), not f64.
|
||||
var raw_recv = node(.{ .identifier = .{ .name = "f64", .is_raw = true } });
|
||||
var raw_fa = node(.{ .field_access = .{ .object = &raw_recv, .field = "epsilon" } });
|
||||
try std.testing.expectEqual(TypeId.s64, l.inferExprType(&raw_fa));
|
||||
try std.testing.expectEqual(TypeId.i64, l.inferExprType(&raw_fa));
|
||||
|
||||
// bare `f64.epsilon` — type_expr receiver, never shadowed → folds to f64.
|
||||
var type_recv = node(.{ .type_expr = .{ .name = "f64" } });
|
||||
@@ -162,9 +162,9 @@ test "expr_typer: raw value binding shadows numeric-limit, bare type still folds
|
||||
// a raw value binding can shadow a builtin numeric type name through
|
||||
// any of three sources — lexical scope, program globals, or module
|
||||
// value constants. The shared `identifierBindsValue` guard consults all three,
|
||||
// so a global `` `f32 := Box.{…} `` and a module-const `` `s16 :: Box.{…} `` each
|
||||
// so a global `` `f32 := Box.{…} `` and a module-const `` `i16 :: Box.{…} `` each
|
||||
// read the value's field (NOT the numeric-limit fold), while a bare `f32.max` /
|
||||
// `s16.max` (a `.type_expr` receiver) still folds. Pins the guard across the two
|
||||
// `i16.max` (a `.type_expr` receiver) still folds. Pins the guard across the two
|
||||
// non-lexical sources the attempt-3 scope-only fix missed.
|
||||
test "expr_typer: global and module-const raw bindings shadow numeric-limit" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
@@ -174,9 +174,9 @@ test "expr_typer: global and module-const raw bindings shadow numeric-limit" {
|
||||
defer module.deinit();
|
||||
var l = Lowering.init(&module);
|
||||
|
||||
// `Box { max: s64 }` — the struct both raw bindings resolve to.
|
||||
// `Box { max: i64 }` — the struct both raw bindings resolve to.
|
||||
const box_fields = [_]ir_mod.types.TypeInfo.StructInfo.Field{
|
||||
.{ .name = module.types.internString("max"), .ty = .s64 },
|
||||
.{ .name = module.types.internString("max"), .ty = .i64 },
|
||||
};
|
||||
const box_ty = module.types.intern(.{ .@"struct" = .{
|
||||
.name = module.types.internString("Box"),
|
||||
@@ -185,25 +185,25 @@ test "expr_typer: global and module-const raw bindings shadow numeric-limit" {
|
||||
|
||||
// GLOBAL raw binding `` `f32 := Box.{…} `` — registered in global_names.
|
||||
try l.program_index.global_names.put("f32", .{ .id = @enumFromInt(0), .ty = box_ty });
|
||||
// MODULE-CONST raw binding `` `s16 :: Box.{…} `` — registered in module_const_map.
|
||||
// MODULE-CONST raw binding `` `i16 :: Box.{…} `` — registered in module_const_map.
|
||||
var const_val = node(.{ .int_literal = .{ .value = 0 } });
|
||||
try l.program_index.module_const_map.put("s16", .{ .value = &const_val, .ty = box_ty });
|
||||
try l.program_index.module_const_map.put("i16", .{ .value = &const_val, .ty = box_ty });
|
||||
|
||||
// The shared guard sees both non-lexical bindings, but not an unbound spelling.
|
||||
try std.testing.expect(l.identifierBindsValue("f32"));
|
||||
try std.testing.expect(l.identifierBindsValue("s16"));
|
||||
try std.testing.expect(l.identifierBindsValue("i16"));
|
||||
try std.testing.expect(!l.identifierBindsValue("u8"));
|
||||
|
||||
// `` `f32.max `` — global raw receiver → ordinary field read, types as s64
|
||||
// `` `f32.max `` — global raw receiver → ordinary field read, types as i64
|
||||
// (the field), not f32 (the fold).
|
||||
var g_recv = node(.{ .identifier = .{ .name = "f32", .is_raw = true } });
|
||||
var g_fa = node(.{ .field_access = .{ .object = &g_recv, .field = "max" } });
|
||||
try std.testing.expectEqual(TypeId.s64, l.inferExprType(&g_fa));
|
||||
try std.testing.expectEqual(TypeId.i64, l.inferExprType(&g_fa));
|
||||
|
||||
// `` `s16.max `` — module-const raw receiver → ordinary field read, types as s64.
|
||||
var c_recv = node(.{ .identifier = .{ .name = "s16", .is_raw = true } });
|
||||
// `` `i16.max `` — module-const raw receiver → ordinary field read, types as i64.
|
||||
var c_recv = node(.{ .identifier = .{ .name = "i16", .is_raw = true } });
|
||||
var c_fa = node(.{ .field_access = .{ .object = &c_recv, .field = "max" } });
|
||||
try std.testing.expectEqual(TypeId.s64, l.inferExprType(&c_fa));
|
||||
try std.testing.expectEqual(TypeId.i64, l.inferExprType(&c_fa));
|
||||
|
||||
// bare `f32.max` — type_expr receiver, never shadowed → folds to f32, even
|
||||
// though a global `` `f32 `` value is bound.
|
||||
@@ -211,9 +211,9 @@ test "expr_typer: global and module-const raw bindings shadow numeric-limit" {
|
||||
var bare_f32_fa = node(.{ .field_access = .{ .object = &bare_f32, .field = "max" } });
|
||||
try std.testing.expectEqual(TypeId.f32, l.inferExprType(&bare_f32_fa));
|
||||
|
||||
// bare `s16.max` — type_expr receiver, never shadowed → folds to the s16
|
||||
// type, even though a module-const `` `s16 `` value is bound.
|
||||
var bare_s16 = node(.{ .type_expr = .{ .name = "s16" } });
|
||||
var bare_s16_fa = node(.{ .field_access = .{ .object = &bare_s16, .field = "max" } });
|
||||
try std.testing.expectEqual(TypeId.s16, l.inferExprType(&bare_s16_fa));
|
||||
// bare `i16.max` — type_expr receiver, never shadowed → folds to the i16
|
||||
// type, even though a module-const `` `i16 `` value is bound.
|
||||
var bare_i16 = node(.{ .type_expr = .{ .name = "i16" } });
|
||||
var bare_i16_fa = node(.{ .field_access = .{ .object = &bare_i16, .field = "max" } });
|
||||
try std.testing.expectEqual(TypeId.i16, l.inferExprType(&bare_i16_fa));
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ pub const ExprTyper = struct {
|
||||
// here is typed by that single owner, not mistyped as `.unresolved`.
|
||||
.call => self.l.inferExprType(node),
|
||||
.string_literal => .string,
|
||||
.int_literal => .s64,
|
||||
.int_literal => .i64,
|
||||
.float_literal => .f64,
|
||||
.bool_literal => .bool,
|
||||
.null_literal => .void,
|
||||
@@ -53,7 +53,7 @@ pub const ExprTyper = struct {
|
||||
// of (lhs, rhs), not the LHS alone — `Lowering.arithResultType`
|
||||
// is the same rule `lowerBinaryOp` applies, so `M + 0.5` types
|
||||
// as `f64` regardless of operand order (was LHS-biased: `M + 0.5`
|
||||
// → s64 while `0.5 + M` → f64).
|
||||
// → i64 while `0.5 + M` → f64).
|
||||
else => Lowering.arithResultType(self.l.inferExprType(bop.lhs), self.l.inferExprType(bop.rhs)),
|
||||
},
|
||||
.unary_op => |uop| switch (uop.op) {
|
||||
@@ -125,17 +125,17 @@ pub const ExprTyper = struct {
|
||||
return .void;
|
||||
},
|
||||
.field_access => |fa| {
|
||||
// Pack-arity intercept: `<pack_name>.len` is s64. Mirrors
|
||||
// Pack-arity intercept: `<pack_name>.len` is i64. Mirrors
|
||||
// the lowerFieldAccess intercept so AST-level type
|
||||
// inference picks the same shape.
|
||||
if (self.l.pack_param_count) |ppc| {
|
||||
if (fa.object.data == .identifier and std.mem.eql(u8, fa.field, "len")) {
|
||||
if (ppc.contains(fa.object.data.identifier.name)) return .s64;
|
||||
if (ppc.contains(fa.object.data.identifier.name)) return .i64;
|
||||
}
|
||||
}
|
||||
// Struct constant access: `Struct.CONST` — mirrors the
|
||||
// lowerFieldAccess intercept (line 3851). Without this,
|
||||
// `Phys.GRAVITY` (f64) inferred as s64 and pack-fn
|
||||
// `Phys.GRAVITY` (f64) inferred as i64 and pack-fn
|
||||
// callers boxed the float into the int slot.
|
||||
if (fa.object.data == .identifier) {
|
||||
const obj_name = fa.object.data.identifier.name;
|
||||
@@ -205,7 +205,7 @@ pub const ExprTyper = struct {
|
||||
obj_ty = opt_info.optional.child;
|
||||
}
|
||||
}
|
||||
if (std.mem.eql(u8, fa.field, "len")) return if (is_opt_chain) self.l.module.types.optionalOf(.s64) else .s64;
|
||||
if (std.mem.eql(u8, fa.field, "len")) return if (is_opt_chain) self.l.module.types.optionalOf(.i64) else .i64;
|
||||
if (std.mem.eql(u8, fa.field, "ptr")) {
|
||||
// .ptr on slice/string → [*]element_type
|
||||
const elem_ty = self.l.getElementType(obj_ty);
|
||||
@@ -311,13 +311,13 @@ pub const ExprTyper = struct {
|
||||
return .unresolved;
|
||||
},
|
||||
.type_expr => |te| {
|
||||
// type_expr can also be a variable reference (e.g., "s1" matches builtin s1 type)
|
||||
// type_expr can also be a variable reference (e.g., "i1" matches builtin i1 type)
|
||||
if (self.l.scope) |scope| {
|
||||
if (scope.lookup(te.name)) |binding| {
|
||||
return binding.ty;
|
||||
}
|
||||
}
|
||||
// A bare type name in expression position (e.g. `s64`,
|
||||
// A bare type name in expression position (e.g. `i64`,
|
||||
// `Point`, `*u8`) is a Type value — IR type `.any`.
|
||||
if (self.l.isKnownTypeName(te.name)) return .any;
|
||||
return .unresolved;
|
||||
@@ -388,7 +388,7 @@ pub const ExprTyper = struct {
|
||||
// `opt ?? default` — result is the inner type when lhs is
|
||||
// optional (the unwrap path's value), else falls back to
|
||||
// the rhs's type. Without this arm pack-fn callers
|
||||
// misinferred float-optional coalesces as s64 and the
|
||||
// misinferred float-optional coalesces as i64 and the
|
||||
// pack mono mangled the arg as int — the actual f64 value
|
||||
// got truncated through Any boxing.
|
||||
const lhs_ty = self.l.inferExprType(nc.lhs);
|
||||
|
||||
@@ -101,7 +101,7 @@ pub const ObjcLowering = struct {
|
||||
/// AFTER stripping `self`.
|
||||
///
|
||||
/// Single-character encodings (the common case):
|
||||
/// v=void B=bool c=s8/BOOL s=s16 i=s32 q=s64
|
||||
/// v=void B=bool c=i8/BOOL s=i16 i=i32 q=i64
|
||||
/// C=u8 S=u16 I=u32 Q=u64 f=f32 d=f64
|
||||
/// @=id #=Class :=SEL *=C string ^v=void* / generic ptr
|
||||
///
|
||||
@@ -168,7 +168,7 @@ pub const ObjcLowering = struct {
|
||||
},
|
||||
.f32 => try out.append(self.l.alloc, 'f'),
|
||||
.f64 => try out.append(self.l.alloc, 'd'),
|
||||
// sx-target arm64 — pointer-sized aliases match s64/u64.
|
||||
// sx-target arm64 — pointer-sized aliases match i64/u64.
|
||||
.isize => try out.append(self.l.alloc, 'q'),
|
||||
.usize => try out.append(self.l.alloc, 'Q'),
|
||||
.pointer => |p| {
|
||||
@@ -387,7 +387,7 @@ pub const ObjcLowering = struct {
|
||||
break :blk fcd.runtime == .objc_class or fcd.runtime == .objc_protocol;
|
||||
};
|
||||
|
||||
// `weak` requires an object pointer — `weak s32` is meaningless and
|
||||
// `weak` requires an object pointer — `weak i32` is meaningless and
|
||||
// would invoke objc_storeWeak on a non-object slot.
|
||||
if (has_weak and !is_object_ptr) {
|
||||
if (self.l.diagnostics) |d| {
|
||||
@@ -396,7 +396,7 @@ pub const ObjcLowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// `copy` requires an object pointer — `copy s32` makes no sense.
|
||||
// `copy` requires an object pointer — `copy i32` makes no sense.
|
||||
if (has_copy and !is_object_ptr) {
|
||||
if (self.l.diagnostics) |d| {
|
||||
const span = ast.Span{ .start = 0, .end = 0 };
|
||||
|
||||
@@ -34,7 +34,7 @@ test "generics: mangleTypeName encodes the mono-key fragment per type shape" {
|
||||
|
||||
// Builtins — the leaf fragments `mangleGenericName` concatenates per
|
||||
// bound type param (`base__<frag>...`).
|
||||
try std.testing.expectEqualStrings("s64", gr.mangleTypeName(.s64));
|
||||
try std.testing.expectEqualStrings("i64", gr.mangleTypeName(.i64));
|
||||
try std.testing.expectEqualStrings("u8", gr.mangleTypeName(.u8));
|
||||
try std.testing.expectEqualStrings("f32", gr.mangleTypeName(.f32));
|
||||
try std.testing.expectEqualStrings("bool", gr.mangleTypeName(.bool));
|
||||
@@ -42,12 +42,12 @@ test "generics: mangleTypeName encodes the mono-key fragment per type shape" {
|
||||
try std.testing.expectEqualStrings("string", gr.mangleTypeName(.string));
|
||||
|
||||
// Compound shapes — prefix + recursive inner fragment.
|
||||
try std.testing.expectEqualStrings("ptr_s64", gr.mangleTypeName(tt.ptrTo(.s64)));
|
||||
try std.testing.expectEqualStrings("opt_s64", gr.mangleTypeName(tt.optionalOf(.s64)));
|
||||
try std.testing.expectEqualStrings("ptr_i64", gr.mangleTypeName(tt.ptrTo(.i64)));
|
||||
try std.testing.expectEqualStrings("opt_i64", gr.mangleTypeName(tt.optionalOf(.i64)));
|
||||
try std.testing.expectEqualStrings("ptr_opt_u8", gr.mangleTypeName(tt.ptrTo(tt.optionalOf(.u8))));
|
||||
try std.testing.expectEqualStrings("SL_f64", gr.mangleTypeName(tt.intern(.{ .slice = .{ .element = .f64 } })));
|
||||
try std.testing.expectEqualStrings("mptr_u8", gr.mangleTypeName(tt.intern(.{ .many_pointer = .{ .element = .u8 } })));
|
||||
try std.testing.expectEqualStrings("AR_4_s32", gr.mangleTypeName(tt.intern(.{ .array = .{ .element = .s32, .length = 4 } })));
|
||||
try std.testing.expectEqualStrings("AR_4_i32", gr.mangleTypeName(tt.intern(.{ .array = .{ .element = .i32, .length = 4 } })));
|
||||
try std.testing.expectEqualStrings("vec_3_f32", gr.mangleTypeName(tt.intern(.{ .vector = .{ .element = .f32, .length = 3 } })));
|
||||
|
||||
// Named aggregate → its declared name.
|
||||
@@ -55,11 +55,11 @@ test "generics: mangleTypeName encodes the mono-key fragment per type shape" {
|
||||
try std.testing.expectEqualStrings("Point", gr.mangleTypeName(pt));
|
||||
|
||||
// Tuple: "tu" + "_<frag>" per field.
|
||||
const tup = tt.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .s64, .bool }, .names = null } });
|
||||
try std.testing.expectEqualStrings("tu_s64_bool", gr.mangleTypeName(tup));
|
||||
const tup = tt.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .i64, .bool }, .names = null } });
|
||||
try std.testing.expectEqualStrings("tu_i64_bool", gr.mangleTypeName(tup));
|
||||
|
||||
// The `Lowering` wrapper delegates here — same result.
|
||||
try std.testing.expectEqualStrings("ptr_s64", l.mangleTypeName(tt.ptrTo(.s64)));
|
||||
try std.testing.expectEqualStrings("ptr_i64", l.mangleTypeName(tt.ptrTo(.i64)));
|
||||
}
|
||||
|
||||
test "generics: inferGenericReturnType binds explicit type args, resolves return, restores bindings" {
|
||||
@@ -100,8 +100,8 @@ test "generics: inferGenericReturnType binds explicit type args, resolves return
|
||||
}
|
||||
}.f;
|
||||
|
||||
const c_s64 = mkCall(alloc, "s64");
|
||||
try std.testing.expectEqual(TypeId.s64, gr.inferGenericReturnType(&fd, &c_s64));
|
||||
const c_i64 = mkCall(alloc, "i64");
|
||||
try std.testing.expectEqual(TypeId.i64, gr.inferGenericReturnType(&fd, &c_i64));
|
||||
const c_f64 = mkCall(alloc, "f64");
|
||||
try std.testing.expectEqual(TypeId.f64, gr.inferGenericReturnType(&fd, &c_f64));
|
||||
|
||||
@@ -145,14 +145,14 @@ test "generics: buildTypeBindings infers a type param from value args, widest wi
|
||||
}
|
||||
}.f;
|
||||
|
||||
// add(1, 2) — both args s64 → T = s64.
|
||||
// add(1, 2) — both args i64 → T = i64.
|
||||
{
|
||||
const args = [_]*const Node{ intLit(alloc, 1), intLit(alloc, 2) };
|
||||
var bindings = gr.buildTypeBindings(&fd, &args);
|
||||
defer bindings.deinit();
|
||||
try std.testing.expectEqual(TypeId.s64, bindings.get("T").?);
|
||||
try std.testing.expectEqual(TypeId.i64, bindings.get("T").?);
|
||||
}
|
||||
// add(1.0, 2) — mixed f64/s64 → widest f64 wins regardless of order.
|
||||
// add(1.0, 2) — mixed f64/i64 → widest f64 wins regardless of order.
|
||||
{
|
||||
const args = [_]*const Node{ floatLit(alloc, 1.0), intLit(alloc, 2) };
|
||||
var bindings = gr.buildTypeBindings(&fd, &args);
|
||||
|
||||
@@ -28,14 +28,14 @@ pub const GenericResolver = struct {
|
||||
|
||||
// ── Mono-key construction ───────────────────────────────────────────
|
||||
|
||||
/// Mangle a TypeId into its mono-key fragment ("s64", "ptr_T", "SL_T",
|
||||
/// Mangle a TypeId into its mono-key fragment ("i64", "ptr_T", "SL_T",
|
||||
/// "AR_n_T", struct name, "tu_X_Y", …). Recursive for compound shapes.
|
||||
pub fn mangleTypeName(self: GenericResolver, ty: TypeId) []const u8 {
|
||||
// Builtin types
|
||||
if (ty == .s8) return "s8";
|
||||
if (ty == .s16) return "s16";
|
||||
if (ty == .s32) return "s32";
|
||||
if (ty == .s64) return "s64";
|
||||
if (ty == .i8) return "i8";
|
||||
if (ty == .i16) return "i16";
|
||||
if (ty == .i32) return "i32";
|
||||
if (ty == .i64) return "i64";
|
||||
if (ty == .u8) return "u8";
|
||||
if (ty == .u16) return "u16";
|
||||
if (ty == .u32) return "u32";
|
||||
@@ -77,7 +77,7 @@ pub const GenericResolver = struct {
|
||||
const inner = self.mangleTypeName(a.element);
|
||||
break :blk std.fmt.allocPrint(self.l.alloc, "AR_{d}_{s}", .{ a.length, inner }) catch "array";
|
||||
},
|
||||
.signed => |w| std.fmt.allocPrint(self.l.alloc, "s{d}", .{w}) catch "signed",
|
||||
.signed => |w| std.fmt.allocPrint(self.l.alloc, "i{d}", .{w}) catch "signed",
|
||||
.unsigned => |w| std.fmt.allocPrint(self.l.alloc, "u{d}", .{w}) catch "unsigned",
|
||||
.optional => |o| blk: {
|
||||
const inner = self.mangleTypeName(o.child);
|
||||
@@ -277,10 +277,10 @@ pub const GenericResolver = struct {
|
||||
// empty `tmp_bindings` is a valid input — non-generic literal
|
||||
// return types (e.g. `walk(..$args) -> string`) still need to
|
||||
// resolve through `resolveTypeWithBindings`, not fall through
|
||||
// to the historical `.s64` default. The default silently
|
||||
// to the historical `.i64` default. The default silently
|
||||
// misclassified pack-fn calls whose return type was a fixed
|
||||
// literal — every consumer (e.g. print's pack-shape mangling)
|
||||
// inferred `s64` and routed the value through the wrong Any
|
||||
// inferred `i64` and routed the value through the wrong Any
|
||||
// tag.
|
||||
var scope = TypeBindingScope.enter(self.l, tmp_bindings);
|
||||
defer scope.exit();
|
||||
|
||||
@@ -19,9 +19,9 @@ test "Ref none sentinel" {
|
||||
test "basic instruction creation" {
|
||||
const inst = Inst{
|
||||
.op = .{ .add = .{ .lhs = Ref.fromIndex(0), .rhs = Ref.fromIndex(1) } },
|
||||
.ty = .s32,
|
||||
.ty = .i32,
|
||||
};
|
||||
try std.testing.expectEqual(types.TypeId.s32, inst.ty);
|
||||
try std.testing.expectEqual(types.TypeId.i32, inst.ty);
|
||||
switch (inst.op) {
|
||||
.add => |bin| {
|
||||
try std.testing.expectEqual(Ref.fromIndex(0), bin.lhs);
|
||||
@@ -38,11 +38,11 @@ test "block creation" {
|
||||
|
||||
block.insts.append(alloc, .{
|
||||
.op = .{ .const_int = 42 },
|
||||
.ty = .s64,
|
||||
.ty = .i64,
|
||||
}) catch unreachable;
|
||||
block.insts.append(alloc, .{
|
||||
.op = .{ .ret = .{ .operand = Ref.fromIndex(0) } },
|
||||
.ty = .s64,
|
||||
.ty = .i64,
|
||||
}) catch unreachable;
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 2), block.insts.items.len);
|
||||
@@ -51,12 +51,12 @@ test "block creation" {
|
||||
test "function creation" {
|
||||
const alloc = std.testing.allocator;
|
||||
const params = &[_]Function.Param{
|
||||
.{ .name = @enumFromInt(1), .ty = .s32 },
|
||||
.{ .name = @enumFromInt(2), .ty = .s32 },
|
||||
.{ .name = @enumFromInt(1), .ty = .i32 },
|
||||
.{ .name = @enumFromInt(2), .ty = .i32 },
|
||||
};
|
||||
var func = Function.init(@enumFromInt(3), params, .s64);
|
||||
var func = Function.init(@enumFromInt(3), params, .i64);
|
||||
defer func.deinit(alloc);
|
||||
|
||||
try std.testing.expectEqual(types.TypeId.s64, func.ret);
|
||||
try std.testing.expectEqual(types.TypeId.i64, func.ret);
|
||||
try std.testing.expectEqual(@as(usize, 2), func.params.len);
|
||||
}
|
||||
|
||||
@@ -150,8 +150,8 @@ pub const Op = union(enum) {
|
||||
bool_not: UnaryOp,
|
||||
|
||||
// ── Conversions ─────────────────────────────────────────────────
|
||||
widen: Conversion, // safe widening (s32 → s64)
|
||||
narrow: Conversion, // truncation via `xx` (s64 → s32)
|
||||
widen: Conversion, // safe widening (i32 → i64)
|
||||
narrow: Conversion, // truncation via `xx` (i64 → i32)
|
||||
bitcast: Conversion, // reinterpret bits
|
||||
int_to_float: Conversion,
|
||||
float_to_int: Conversion,
|
||||
@@ -282,7 +282,7 @@ pub const Store = struct {
|
||||
val: Ref,
|
||||
/// Declared type of the value being stored. Threaded through so the
|
||||
/// interp's raw-pointer store knows the destination byte width — a
|
||||
/// `.int` Value alone is ambiguous (s8/s16/s32/s64/u*/usize/pointer
|
||||
/// `.int` Value alone is ambiguous (i8/i16/i32/i64/u*/usize/pointer
|
||||
/// all flatten to `.int`). The LLVM emitter ignores this (LLVM knows
|
||||
/// the width from the SSA value's type already).
|
||||
val_ty: TypeId = .void,
|
||||
@@ -502,8 +502,8 @@ pub const Function = struct {
|
||||
/// IR with this set — sx-side `..T` params are slice-packed before
|
||||
/// lowering, so anything that survives is the C calling convention's
|
||||
/// `...`. emit_llvm passes `is_var_arg=1` to `LLVMFunctionType`; call
|
||||
/// sites apply the standard default argument promotions (s8/s16/bool →
|
||||
/// s32, f32 → f64) to extras past the fixed param count.
|
||||
/// sites apply the standard default argument promotions (i8/i16/bool →
|
||||
/// i32, f32 → f64) to extras past the fixed param count.
|
||||
is_variadic: bool = false,
|
||||
/// True if `params[0]` is the synthetic `__sx_ctx: *Context`
|
||||
/// parameter that every default-conv sx function receives. Callers
|
||||
|
||||
@@ -34,15 +34,15 @@ test "interpret: compute(5) = 25" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func compute(x: s64) -> s64 { return x * x; }
|
||||
const params = &[_]Function.Param{.{ .name = str(&module, "compute"), .ty = .s64 }};
|
||||
_ = b.beginFunction(str(&module, "compute"), params, .s64);
|
||||
// func compute(x: i64) -> i64 { return x * x; }
|
||||
const params = &[_]Function.Param{.{ .name = str(&module, "compute"), .ty = .i64 }};
|
||||
_ = b.beginFunction(str(&module, "compute"), params, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const x_ref = Ref.fromIndex(0);
|
||||
const result = b.mul(x_ref, x_ref, .s64);
|
||||
b.ret(result, .s64);
|
||||
const result = b.mul(x_ref, x_ref, .i64);
|
||||
b.ret(result, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -61,8 +61,8 @@ test "interpret: if/else branching" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
const params = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .s64 }};
|
||||
_ = b.beginFunction(str(&module, "abs"), params, .s64);
|
||||
const params = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .i64 }};
|
||||
_ = b.beginFunction(str(&module, "abs"), params, .i64);
|
||||
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
const then_bb = b.appendBlock(str(&module, "then"), &.{});
|
||||
@@ -70,16 +70,16 @@ test "interpret: if/else branching" {
|
||||
|
||||
b.switchToBlock(entry);
|
||||
const x = Ref.fromIndex(0);
|
||||
const zero = b.constInt(0, .s64);
|
||||
const zero = b.constInt(0, .i64);
|
||||
const is_neg = b.cmpLt(x, zero);
|
||||
b.condBr(is_neg, then_bb, &.{}, else_bb, &.{});
|
||||
|
||||
b.switchToBlock(then_bb);
|
||||
const neg_x = b.emit(.{ .neg = .{ .operand = x } }, .s64);
|
||||
b.ret(neg_x, .s64);
|
||||
const neg_x = b.emit(.{ .neg = .{ .operand = x } }, .i64);
|
||||
b.ret(neg_x, .i64);
|
||||
|
||||
b.switchToBlock(else_bb);
|
||||
b.ret(x, .s64);
|
||||
b.ret(x, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -101,30 +101,30 @@ test "interpret: function calling another function" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func square(x: s64) -> s64 { return x * x; }
|
||||
const params_sq = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .s64 }};
|
||||
_ = b.beginFunction(str(&module, "square"), params_sq, .s64);
|
||||
// func square(x: i64) -> i64 { return x * x; }
|
||||
const params_sq = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .i64 }};
|
||||
_ = b.beginFunction(str(&module, "square"), params_sq, .i64);
|
||||
const entry1 = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry1);
|
||||
const x = Ref.fromIndex(0);
|
||||
const sq = b.mul(x, x, .s64);
|
||||
b.ret(sq, .s64);
|
||||
const sq = b.mul(x, x, .i64);
|
||||
b.ret(sq, .i64);
|
||||
b.finalize();
|
||||
|
||||
// func sum_of_squares(a, b) -> s64 { return square(a) + square(b); }
|
||||
// func sum_of_squares(a, b) -> i64 { return square(a) + square(b); }
|
||||
const params_ss = &[_]Function.Param{
|
||||
.{ .name = str(&module, "a"), .ty = .s64 },
|
||||
.{ .name = str(&module, "b"), .ty = .s64 },
|
||||
.{ .name = str(&module, "a"), .ty = .i64 },
|
||||
.{ .name = str(&module, "b"), .ty = .i64 },
|
||||
};
|
||||
_ = b.beginFunction(str(&module, "sum_of_squares"), params_ss, .s64);
|
||||
_ = b.beginFunction(str(&module, "sum_of_squares"), params_ss, .i64);
|
||||
const entry2 = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry2);
|
||||
const a = Ref.fromIndex(0);
|
||||
const b_param = Ref.fromIndex(1);
|
||||
const sq_a = b.call(FuncId.fromIndex(0), &.{a}, .s64);
|
||||
const sq_b = b.call(FuncId.fromIndex(0), &.{b_param}, .s64);
|
||||
const sum = b.add(sq_a, sq_b, .s64);
|
||||
b.ret(sum, .s64);
|
||||
const sq_a = b.call(FuncId.fromIndex(0), &.{a}, .i64);
|
||||
const sq_b = b.call(FuncId.fromIndex(0), &.{b_param}, .i64);
|
||||
const sum = b.add(sq_a, sq_b, .i64);
|
||||
b.ret(sum, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -143,19 +143,19 @@ test "interpret: alloca/store/load" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(str(&module, "test"), &.{}, .s64);
|
||||
_ = b.beginFunction(str(&module, "test"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const slot = b.alloca(.s64);
|
||||
const ten = b.constInt(10, .s64);
|
||||
const slot = b.alloca(.i64);
|
||||
const ten = b.constInt(10, .i64);
|
||||
b.store(slot, ten);
|
||||
const loaded = b.load(slot, .s64);
|
||||
const five = b.constInt(5, .s64);
|
||||
const sum = b.add(loaded, five, .s64);
|
||||
const loaded = b.load(slot, .i64);
|
||||
const five = b.constInt(5, .i64);
|
||||
const sum = b.add(loaded, five, .i64);
|
||||
b.store(slot, sum);
|
||||
const result = b.load(slot, .s64);
|
||||
b.ret(result, .s64);
|
||||
const result = b.load(slot, .i64);
|
||||
b.ret(result, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -168,7 +168,7 @@ test "interpret: alloca/store/load" {
|
||||
// ── Comptime parity tests ───────────────────────────────────────────────
|
||||
|
||||
// ── Test: while loop (sumOf10 from 15-while.sx) ─────────────────────────
|
||||
// sumOf10 :: () -> s32 { i:=1; s:=0; while i<=10 { s+=i; i+=1; } s; }
|
||||
// sumOf10 :: () -> i32 { i:=1; s:=0; while i<=10 { s+=i; i+=1; } s; }
|
||||
// Expected: 55
|
||||
|
||||
test "comptime: while loop — sumOf10 = 55" {
|
||||
@@ -179,7 +179,7 @@ test "comptime: while loop — sumOf10 = 55" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(str(&module, "sumOf10"), &.{}, .s64);
|
||||
_ = b.beginFunction(str(&module, "sumOf10"), &.{}, .i64);
|
||||
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
const hdr = b.appendBlock(str(&module, "while.hdr"), &.{});
|
||||
@@ -188,37 +188,37 @@ test "comptime: while loop — sumOf10 = 55" {
|
||||
|
||||
// entry: i=1, s=0, br while.hdr
|
||||
b.switchToBlock(entry);
|
||||
const i_slot = b.alloca(.s64);
|
||||
const one = b.constInt(1, .s64);
|
||||
const i_slot = b.alloca(.i64);
|
||||
const one = b.constInt(1, .i64);
|
||||
b.store(i_slot, one);
|
||||
const s_slot = b.alloca(.s64);
|
||||
const zero = b.constInt(0, .s64);
|
||||
const s_slot = b.alloca(.i64);
|
||||
const zero = b.constInt(0, .i64);
|
||||
b.store(s_slot, zero);
|
||||
b.br(hdr, &.{});
|
||||
|
||||
// while.hdr: if i <= 10 → body, else → exit
|
||||
b.switchToBlock(hdr);
|
||||
const i_load = b.load(i_slot, .s64);
|
||||
const ten = b.constInt(10, .s64);
|
||||
const i_load = b.load(i_slot, .i64);
|
||||
const ten = b.constInt(10, .i64);
|
||||
const cond = b.emit(.{ .cmp_le = .{ .lhs = i_load, .rhs = ten } }, .bool);
|
||||
b.condBr(cond, body, &.{}, exit, &.{});
|
||||
|
||||
// while.body: s += i; i += 1; br while.hdr
|
||||
b.switchToBlock(body);
|
||||
const s_load = b.load(s_slot, .s64);
|
||||
const i_load2 = b.load(i_slot, .s64);
|
||||
const s_new = b.add(s_load, i_load2, .s64);
|
||||
const s_load = b.load(s_slot, .i64);
|
||||
const i_load2 = b.load(i_slot, .i64);
|
||||
const s_new = b.add(s_load, i_load2, .i64);
|
||||
b.store(s_slot, s_new);
|
||||
const i_load3 = b.load(i_slot, .s64);
|
||||
const one2 = b.constInt(1, .s64);
|
||||
const i_new = b.add(i_load3, one2, .s64);
|
||||
const i_load3 = b.load(i_slot, .i64);
|
||||
const one2 = b.constInt(1, .i64);
|
||||
const i_new = b.add(i_load3, one2, .i64);
|
||||
b.store(i_slot, i_new);
|
||||
b.br(hdr, &.{});
|
||||
|
||||
// while.exit: return s
|
||||
b.switchToBlock(exit);
|
||||
const s_final = b.load(s_slot, .s64);
|
||||
b.ret(s_final, .s64);
|
||||
const s_final = b.load(s_slot, .i64);
|
||||
b.ret(s_final, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -228,7 +228,7 @@ test "comptime: while loop — sumOf10 = 55" {
|
||||
}
|
||||
|
||||
// ── Test: optional coalesce (ct_sum from 32-optionals.sx) ────────────────
|
||||
// ct_sum :: () -> s32 { x:?s32=42; y:?s32=null; return (x??0)+(y??99); }
|
||||
// ct_sum :: () -> i32 { x:?i32=42; y:?i32=null; return (x??0)+(y??99); }
|
||||
// Expected: 42 + 99 = 141
|
||||
|
||||
test "comptime: optional coalesce — ct_sum = 141" {
|
||||
@@ -239,33 +239,33 @@ test "comptime: optional coalesce — ct_sum = 141" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(str(&module, "ct_sum"), &.{}, .s64);
|
||||
_ = b.beginFunction(str(&module, "ct_sum"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
// x: ?s32 = 42 → alloca, store 42
|
||||
const x_slot = b.alloca(.s64);
|
||||
const forty_two = b.constInt(42, .s64);
|
||||
// x: ?i32 = 42 → alloca, store 42
|
||||
const x_slot = b.alloca(.i64);
|
||||
const forty_two = b.constInt(42, .i64);
|
||||
b.store(x_slot, forty_two);
|
||||
|
||||
// y: ?s32 = null → alloca, store null
|
||||
const y_slot = b.alloca(.s64);
|
||||
const null_val = b.constNull(.s64);
|
||||
// y: ?i32 = null → alloca, store null
|
||||
const y_slot = b.alloca(.i64);
|
||||
const null_val = b.constNull(.i64);
|
||||
b.store(y_slot, null_val);
|
||||
|
||||
// (x ?? 0)
|
||||
const x_load = b.load(x_slot, .s64);
|
||||
const zero = b.constInt(0, .s64);
|
||||
const x_coalesced = b.emit(.{ .optional_coalesce = .{ .lhs = x_load, .rhs = zero } }, .s64);
|
||||
const x_load = b.load(x_slot, .i64);
|
||||
const zero = b.constInt(0, .i64);
|
||||
const x_coalesced = b.emit(.{ .optional_coalesce = .{ .lhs = x_load, .rhs = zero } }, .i64);
|
||||
|
||||
// (y ?? 99)
|
||||
const y_load = b.load(y_slot, .s64);
|
||||
const ninety_nine = b.constInt(99, .s64);
|
||||
const y_coalesced = b.emit(.{ .optional_coalesce = .{ .lhs = y_load, .rhs = ninety_nine } }, .s64);
|
||||
const y_load = b.load(y_slot, .i64);
|
||||
const ninety_nine = b.constInt(99, .i64);
|
||||
const y_coalesced = b.emit(.{ .optional_coalesce = .{ .lhs = y_load, .rhs = ninety_nine } }, .i64);
|
||||
|
||||
// return x_coalesced + y_coalesced
|
||||
const sum = b.add(x_coalesced, y_coalesced, .s64);
|
||||
b.ret(sum, .s64);
|
||||
const sum = b.add(x_coalesced, y_coalesced, .i64);
|
||||
b.ret(sum, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -275,7 +275,7 @@ test "comptime: optional coalesce — ct_sum = 141" {
|
||||
}
|
||||
|
||||
// ── Test: optional unwrap (ct_opt_unwrap from 50-smoke.sx) ───────────────
|
||||
// ct_opt_unwrap :: () -> s32 { x:?s32 = 77; return x!; }
|
||||
// ct_opt_unwrap :: () -> i32 { x:?i32 = 77; return x!; }
|
||||
// Expected: 77
|
||||
|
||||
test "comptime: optional unwrap — 77" {
|
||||
@@ -286,17 +286,17 @@ test "comptime: optional unwrap — 77" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(str(&module, "ct_opt_unwrap"), &.{}, .s64);
|
||||
_ = b.beginFunction(str(&module, "ct_opt_unwrap"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const slot = b.alloca(.s64);
|
||||
const val77 = b.constInt(77, .s64);
|
||||
const slot = b.alloca(.i64);
|
||||
const val77 = b.constInt(77, .i64);
|
||||
b.store(slot, val77);
|
||||
|
||||
const loaded = b.load(slot, .s64);
|
||||
const unwrapped = b.optionalUnwrap(loaded, .s64);
|
||||
b.ret(unwrapped, .s64);
|
||||
const loaded = b.load(slot, .i64);
|
||||
const unwrapped = b.optionalUnwrap(loaded, .i64);
|
||||
b.ret(unwrapped, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -306,7 +306,7 @@ test "comptime: optional unwrap — 77" {
|
||||
}
|
||||
|
||||
// ── Test: recursive fibonacci ────────────────────────────────────────────
|
||||
// fib :: (n: s64) -> s64 { if n <= 1 return n; return fib(n-1) + fib(n-2); }
|
||||
// fib :: (n: i64) -> i64 { if n <= 1 return n; return fib(n-1) + fib(n-2); }
|
||||
// Expected: fib(10) = 55
|
||||
|
||||
test "comptime: recursive fibonacci — fib(10) = 55" {
|
||||
@@ -317,8 +317,8 @@ test "comptime: recursive fibonacci — fib(10) = 55" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
const params = &[_]Function.Param{.{ .name = str(&module, "n"), .ty = .s64 }};
|
||||
_ = b.beginFunction(str(&module, "fib"), params, .s64);
|
||||
const params = &[_]Function.Param{.{ .name = str(&module, "n"), .ty = .i64 }};
|
||||
_ = b.beginFunction(str(&module, "fib"), params, .i64);
|
||||
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
const base_bb = b.appendBlock(str(&module, "base"), &.{});
|
||||
@@ -327,23 +327,23 @@ test "comptime: recursive fibonacci — fib(10) = 55" {
|
||||
// entry: if n <= 1 → base, else → recurse
|
||||
b.switchToBlock(entry);
|
||||
const n = Ref.fromIndex(0);
|
||||
const one = b.constInt(1, .s64);
|
||||
const one = b.constInt(1, .i64);
|
||||
const is_base = b.emit(.{ .cmp_le = .{ .lhs = n, .rhs = one } }, .bool);
|
||||
b.condBr(is_base, base_bb, &.{}, rec_bb, &.{});
|
||||
|
||||
// base: return n
|
||||
b.switchToBlock(base_bb);
|
||||
b.ret(n, .s64);
|
||||
b.ret(n, .i64);
|
||||
|
||||
// recurse: return fib(n-1) + fib(n-2)
|
||||
b.switchToBlock(rec_bb);
|
||||
const n_minus_1 = b.sub(n, one, .s64);
|
||||
const two = b.constInt(2, .s64);
|
||||
const n_minus_2 = b.sub(n, two, .s64);
|
||||
const fib1 = b.call(FuncId.fromIndex(0), &.{n_minus_1}, .s64);
|
||||
const fib2 = b.call(FuncId.fromIndex(0), &.{n_minus_2}, .s64);
|
||||
const sum = b.add(fib1, fib2, .s64);
|
||||
b.ret(sum, .s64);
|
||||
const n_minus_1 = b.sub(n, one, .i64);
|
||||
const two = b.constInt(2, .i64);
|
||||
const n_minus_2 = b.sub(n, two, .i64);
|
||||
const fib1 = b.call(FuncId.fromIndex(0), &.{n_minus_1}, .i64);
|
||||
const fib2 = b.call(FuncId.fromIndex(0), &.{n_minus_2}, .i64);
|
||||
const sum = b.add(fib1, fib2, .i64);
|
||||
b.ret(sum, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -353,7 +353,7 @@ test "comptime: recursive fibonacci — fib(10) = 55" {
|
||||
}
|
||||
|
||||
// ── Test: compute(5) = 7 (from 05-run.sx) ──────────────────────────────
|
||||
// compute :: (v: s32) -> s32 => v + 2;
|
||||
// compute :: (v: i32) -> i32 => v + 2;
|
||||
// Expected: compute(5) = 7
|
||||
|
||||
test "comptime: compute(5) = 7" {
|
||||
@@ -364,15 +364,15 @@ test "comptime: compute(5) = 7" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
const params = &[_]Function.Param{.{ .name = str(&module, "v"), .ty = .s64 }};
|
||||
_ = b.beginFunction(str(&module, "compute"), params, .s64);
|
||||
const params = &[_]Function.Param{.{ .name = str(&module, "v"), .ty = .i64 }};
|
||||
_ = b.beginFunction(str(&module, "compute"), params, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const v = Ref.fromIndex(0);
|
||||
const two = b.constInt(2, .s64);
|
||||
const result = b.add(v, two, .s64);
|
||||
b.ret(result, .s64);
|
||||
const two = b.constInt(2, .i64);
|
||||
const result = b.add(v, two, .i64);
|
||||
b.ret(result, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -382,7 +382,7 @@ test "comptime: compute(5) = 7" {
|
||||
}
|
||||
|
||||
// ── Test: chained comptime (CT_CHAIN from 50-smoke.sx) ───────────────────
|
||||
// add :: (a: s32, b: s32) -> s32 => a + b;
|
||||
// add :: (a: i32, b: i32) -> i32 => a + b;
|
||||
// CT_VAL :: #run add(10, 15); → 25
|
||||
// CT_CHAIN :: #run add(CT_VAL, 5); → 30
|
||||
// Simulates calling add(25, 5) to verify chaining works.
|
||||
@@ -395,18 +395,18 @@ test "comptime: chained — add(add(10,15), 5) = 30" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func add(a, b) -> s64 { return a + b; }
|
||||
// func add(a, b) -> i64 { return a + b; }
|
||||
const params = &[_]Function.Param{
|
||||
.{ .name = str(&module, "a"), .ty = .s64 },
|
||||
.{ .name = str(&module, "b"), .ty = .s64 },
|
||||
.{ .name = str(&module, "a"), .ty = .i64 },
|
||||
.{ .name = str(&module, "b"), .ty = .i64 },
|
||||
};
|
||||
_ = b.beginFunction(str(&module, "add"), params, .s64);
|
||||
_ = b.beginFunction(str(&module, "add"), params, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
const a = Ref.fromIndex(0);
|
||||
const b_ref = Ref.fromIndex(1);
|
||||
const sum = b.add(a, b_ref, .s64);
|
||||
b.ret(sum, .s64);
|
||||
const sum = b.add(a, b_ref, .i64);
|
||||
b.ret(sum, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -433,20 +433,20 @@ test "comptime: struct init and field access — 7" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(str(&module, "test_struct"), &.{}, .s64);
|
||||
_ = b.beginFunction(str(&module, "test_struct"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
// Point{x: 3, y: 4}
|
||||
const three = b.constInt(3, .s64);
|
||||
const four = b.constInt(4, .s64);
|
||||
const point = b.structInit(&.{ three, four }, .s64);
|
||||
const three = b.constInt(3, .i64);
|
||||
const four = b.constInt(4, .i64);
|
||||
const point = b.structInit(&.{ three, four }, .i64);
|
||||
|
||||
// p.x + p.y
|
||||
const px = b.structGet(point, 0, .s64);
|
||||
const py = b.structGet(point, 1, .s64);
|
||||
const sum = b.add(px, py, .s64);
|
||||
b.ret(sum, .s64);
|
||||
const px = b.structGet(point, 0, .i64);
|
||||
const py = b.structGet(point, 1, .i64);
|
||||
const sum = b.add(px, py, .i64);
|
||||
b.ret(sum, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -538,14 +538,14 @@ test "comptime: negation — int and float" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func neg_int(x: s64) -> s64 { return -x; }
|
||||
const params = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .s64 }};
|
||||
_ = b.beginFunction(str(&module, "neg_int"), params, .s64);
|
||||
// func neg_int(x: i64) -> i64 { return -x; }
|
||||
const params = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .i64 }};
|
||||
_ = b.beginFunction(str(&module, "neg_int"), params, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
const x = Ref.fromIndex(0);
|
||||
const neg = b.emit(.{ .neg = .{ .operand = x } }, .s64);
|
||||
b.ret(neg, .s64);
|
||||
const neg = b.emit(.{ .neg = .{ .operand = x } }, .i64);
|
||||
b.ret(neg, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -564,14 +564,14 @@ test "comptime: modulo — 17 mod 5 = 2" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(str(&module, "test_mod"), &.{}, .s64);
|
||||
_ = b.beginFunction(str(&module, "test_mod"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const seventeen = b.constInt(17, .s64);
|
||||
const five = b.constInt(5, .s64);
|
||||
const result = b.emit(.{ .mod = .{ .lhs = seventeen, .rhs = five } }, .s64);
|
||||
b.ret(result, .s64);
|
||||
const seventeen = b.constInt(17, .i64);
|
||||
const five = b.constInt(5, .i64);
|
||||
const result = b.emit(.{ .mod = .{ .lhs = seventeen, .rhs = five } }, .i64);
|
||||
b.ret(result, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -591,8 +591,8 @@ test "comptime: switch_br dispatch" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
const params = &[_]Function.Param{.{ .name = str(&module, "tag"), .ty = .s64 }};
|
||||
_ = b.beginFunction(str(&module, "dispatch"), params, .s64);
|
||||
const params = &[_]Function.Param{.{ .name = str(&module, "tag"), .ty = .i64 }};
|
||||
_ = b.beginFunction(str(&module, "dispatch"), params, .i64);
|
||||
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
const case0 = b.appendBlock(str(&module, "case0"), &.{});
|
||||
@@ -607,16 +607,16 @@ test "comptime: switch_br dispatch" {
|
||||
}, default, &.{});
|
||||
|
||||
b.switchToBlock(case0);
|
||||
const ten = b.constInt(10, .s64);
|
||||
b.ret(ten, .s64);
|
||||
const ten = b.constInt(10, .i64);
|
||||
b.ret(ten, .i64);
|
||||
|
||||
b.switchToBlock(case1);
|
||||
const twenty = b.constInt(20, .s64);
|
||||
b.ret(twenty, .s64);
|
||||
const twenty = b.constInt(20, .i64);
|
||||
b.ret(twenty, .i64);
|
||||
|
||||
b.switchToBlock(default);
|
||||
const thirty = b.constInt(30, .s64);
|
||||
b.ret(thirty, .s64);
|
||||
const thirty = b.constInt(30, .i64);
|
||||
b.ret(thirty, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -642,14 +642,14 @@ test "comptime: enum init and tag" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(str(&module, "test_enum"), &.{}, .s64);
|
||||
_ = b.beginFunction(str(&module, "test_enum"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
// Create enum with tag=2, no payload
|
||||
const e = b.enumInit(2, Ref.none, .s64);
|
||||
const tag = b.emit(.{ .enum_tag = .{ .operand = e } }, .s64);
|
||||
b.ret(tag, .s64);
|
||||
const e = b.enumInit(2, Ref.none, .i64);
|
||||
const tag = b.emit(.{ .enum_tag = .{ .operand = e } }, .i64);
|
||||
b.ret(tag, .i64);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -668,14 +668,14 @@ test "comptime: widen/narrow passthrough" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(str(&module, "test_conv"), &.{}, .s64);
|
||||
_ = b.beginFunction(str(&module, "test_conv"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const val = b.constInt(42, .s32);
|
||||
const widened = b.emit(.{ .widen = .{ .operand = val, .from = .s32, .to = .s64 } }, .s64);
|
||||
const narrowed = b.emit(.{ .narrow = .{ .operand = widened, .from = .s64, .to = .s32 } }, .s32);
|
||||
b.ret(narrowed, .s32);
|
||||
const val = b.constInt(42, .i32);
|
||||
const widened = b.emit(.{ .widen = .{ .operand = val, .from = .i32, .to = .i64 } }, .i64);
|
||||
const narrowed = b.emit(.{ .narrow = .{ .operand = widened, .from = .i64, .to = .i32 } }, .i32);
|
||||
b.ret(narrowed, .i32);
|
||||
b.finalize();
|
||||
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
@@ -694,13 +694,13 @@ test "comptime: const_type yields type_tag" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// Build a fn that returns `s64` as a Type-typed Any value (matches
|
||||
// Build a fn that returns `i64` as a Type-typed Any value (matches
|
||||
// the .any IR type assigned by `constType`). The interp returns the
|
||||
// raw Value; we assert on the variant.
|
||||
_ = b.beginFunction(str(&module, "test_type_tag"), &.{}, .any);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
const t = b.constType(.s64);
|
||||
const t = b.constType(.i64);
|
||||
b.ret(t, .any);
|
||||
b.finalize();
|
||||
|
||||
@@ -711,7 +711,7 @@ test "comptime: const_type yields type_tag" {
|
||||
// The Value MUST be a .type_tag, not an .int — proves the variant
|
||||
// is honestly distinguished. asTypeId returns the inner TypeId;
|
||||
// asInt MUST return null (no coercion).
|
||||
try std.testing.expectEqual(@as(?TypeId, .s64), result.asTypeId());
|
||||
try std.testing.expectEqual(@as(?TypeId, .i64), result.asTypeId());
|
||||
try std.testing.expectEqual(@as(?i64, null), result.asInt());
|
||||
}
|
||||
|
||||
@@ -725,23 +725,23 @@ test "comptime: type_tag comparison" {
|
||||
defer module.deinit();
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// Returns (s64 == s64) — should yield bool true via the new
|
||||
// Returns (i64 == i64) — should yield bool true via the new
|
||||
// evalCmp arm for .type_tag operands.
|
||||
_ = b.beginFunction(str(&module, "test_type_eq_true"), &.{}, .bool);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
const a = b.constType(.s64);
|
||||
const c = b.constType(.s64);
|
||||
const a = b.constType(.i64);
|
||||
const c = b.constType(.i64);
|
||||
const eq = b.cmpEq(a, c);
|
||||
b.ret(eq, .bool);
|
||||
b.finalize();
|
||||
|
||||
// Different TypeIds: (s64 == s32) should be false.
|
||||
// Different TypeIds: (i64 == i32) should be false.
|
||||
_ = b.beginFunction(str(&module, "test_type_eq_false"), &.{}, .bool);
|
||||
const entry2 = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry2);
|
||||
const a2 = b.constType(.s64);
|
||||
const c2 = b.constType(.s32);
|
||||
const a2 = b.constType(.i64);
|
||||
const c2 = b.constType(.i32);
|
||||
const eq2 = b.cmpEq(a2, c2);
|
||||
b.ret(eq2, .bool);
|
||||
b.finalize();
|
||||
@@ -767,7 +767,7 @@ test "comptime: type_name builtin on type_tag" {
|
||||
_ = b.beginFunction(str(&module, "test_type_name"), &.{}, .string);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
const t = b.constType(.s64);
|
||||
const t = b.constType(.i64);
|
||||
var args = [_]inst_mod.Ref{t};
|
||||
const r = b.callBuiltin(.type_name, &args, .string);
|
||||
b.ret(r, .string);
|
||||
@@ -776,7 +776,7 @@ test "comptime: type_name builtin on type_tag" {
|
||||
var interp = Interpreter.init(&module, alloc);
|
||||
defer interp.deinit();
|
||||
const result = try interp.call(FuncId.fromIndex(0), &.{});
|
||||
try std.testing.expectEqualStrings("s64", result.asString(&interp).?);
|
||||
try std.testing.expectEqualStrings("i64", result.asString(&interp).?);
|
||||
}
|
||||
|
||||
// ── Test: type_eq builtin on two .type_tag operands ────────────────────
|
||||
@@ -809,7 +809,7 @@ test "comptime: type_eq builtin on type_tag values" {
|
||||
// A reflection builtin on an Any must report the type OF a held value (the
|
||||
// tag) and only read the payload when the Any holds a Type value (tag ==
|
||||
// `.any`). Regression: a boxed value like
|
||||
// `av : Any = 6` (`{ tag = s64, value = 6 }`) must resolve to `s64`, NOT
|
||||
// `av : Any = 6` (`{ tag = i64, value = 6 }`) must resolve to `i64`, NOT
|
||||
// `types[6]` (`u8`).
|
||||
test "reflect: reflectTypeId branches on the Any tag" {
|
||||
const any_idx: i64 = @intCast(TypeId.any.index());
|
||||
@@ -817,10 +817,10 @@ test "reflect: reflectTypeId branches on the Any tag" {
|
||||
// Native first-class Type value → the held TypeId directly.
|
||||
try std.testing.expectEqual(@as(?TypeId, .u64), (Value{ .type_tag = .u64 }).reflectTypeId());
|
||||
|
||||
// Any holding a VALUE: `{ tag = s64, value = 6 }` → s64 (the tag),
|
||||
// Any holding a VALUE: `{ tag = i64, value = 6 }` → i64 (the tag),
|
||||
// never `types[6]` (u8). This is the bug the fix closes.
|
||||
var held_value = [_]Value{ .{ .int = @intCast(TypeId.s64.index()) }, .{ .int = 6 } };
|
||||
try std.testing.expectEqual(@as(?TypeId, .s64), (Value{ .aggregate = &held_value }).reflectTypeId());
|
||||
var held_value = [_]Value{ .{ .int = @intCast(TypeId.i64.index()) }, .{ .int = 6 } };
|
||||
try std.testing.expectEqual(@as(?TypeId, .i64), (Value{ .aggregate = &held_value }).reflectTypeId());
|
||||
|
||||
// Any holding a VALUE of an unsigned type: `{ tag = u32, value = 7 }` → u32.
|
||||
var held_u32 = [_]Value{ .{ .int = @intCast(TypeId.u32.index()) }, .{ .int = 7 } };
|
||||
|
||||
@@ -98,7 +98,7 @@ pub const Value = union(enum) {
|
||||
/// box carries a *Type value* (the `box_any(.., .any)` / `const_type`
|
||||
/// shape) → the TypeId is the payload; otherwise the box carries a
|
||||
/// *runtime value* whose type IS the tag → the tag is the TypeId. This
|
||||
/// makes `type_name(av)` for `av : Any = 6` report `s64` (the held
|
||||
/// makes `type_name(av)` for `av : Any = 6` report `i64` (the held
|
||||
/// value's type) while `type_name(type_of(x))` still names the type.
|
||||
/// Returns null when `self` is neither shape (the caller bails loudly).
|
||||
pub fn reflectTypeId(self: Value) ?TypeId {
|
||||
@@ -266,7 +266,7 @@ pub const Interpreter = struct {
|
||||
const width = self.module.types.typeSizeBytes(val_ty);
|
||||
switch (val) {
|
||||
.int => |v| {
|
||||
// Width is whatever the declared IR type says (s8..s64,
|
||||
// Width is whatever the declared IR type says (i8..i64,
|
||||
// u8..u64, usize, pointer-as-int, bool-after-extension).
|
||||
// The Value tag itself is .int regardless.
|
||||
if (width == 0 or width > 8) return bailDetail("comptime store of int through raw pointer: unexpected declared width (expected 1..8 bytes)");
|
||||
@@ -535,7 +535,7 @@ pub const Interpreter = struct {
|
||||
}
|
||||
return .void_val;
|
||||
}
|
||||
if (ret == .s8 or ret == .s16 or ret == .s32 or ret == .s64 or
|
||||
if (ret == .i8 or ret == .i16 or ret == .i32 or ret == .i64 or
|
||||
ret == .u8 or ret == .u16 or ret == .u32 or ret == .u64 or
|
||||
ret == .usize or ret == .isize)
|
||||
{
|
||||
@@ -1913,7 +1913,7 @@ pub const Interpreter = struct {
|
||||
// Any-boxed Type (`{ .any, tid }`), or an Any holding a
|
||||
// runtime value (`{ tag, value }`, where the tag IS the
|
||||
// value's type). `reflectTypeId` reads the runtime tag so
|
||||
// `type_name(av)` for `av : Any = 6` names `s64`, not the
|
||||
// `type_name(av)` for `av : Any = 6` names `i64`, not the
|
||||
// type whose index equals the payload.
|
||||
const tid = arg.reflectTypeId() orelse
|
||||
return bailDetail("comptime type_name: argument is not a Type value or boxed value (expected `.type_tag` or Any aggregate)");
|
||||
@@ -1939,7 +1939,7 @@ pub const Interpreter = struct {
|
||||
// (`{ tag, value }`, where the tag IS the value's type).
|
||||
// `reflectTypeId` reads the runtime tag so
|
||||
// `type_is_unsigned(av)` for `av : Any = 6` answers about
|
||||
// `s64`, not the type whose index equals the payload.
|
||||
// `i64`, not the type whose index equals the payload.
|
||||
const tid = arg.reflectTypeId() orelse
|
||||
return bailDetail("comptime type_is_unsigned: argument is not a Type value or boxed value (expected `.type_tag` or Any aggregate)");
|
||||
return .{ .value = .{ .boolean = self.module.types.isUnsignedInt(tid) } };
|
||||
|
||||
@@ -51,12 +51,12 @@ fn expectType(name: []const u8, expected: []const u8) !void {
|
||||
test "primitive descriptors" {
|
||||
try expectType("void", "V");
|
||||
try expectType("bool", "Z");
|
||||
try expectType("s8", "B");
|
||||
try expectType("i8", "B");
|
||||
try expectType("u8", "B");
|
||||
try expectType("s16", "S");
|
||||
try expectType("i16", "S");
|
||||
try expectType("u16", "C");
|
||||
try expectType("s32", "I");
|
||||
try expectType("s64", "J");
|
||||
try expectType("i32", "I");
|
||||
try expectType("i64", "J");
|
||||
try expectType("f32", "F");
|
||||
try expectType("f64", "D");
|
||||
}
|
||||
@@ -105,8 +105,8 @@ test "slice of primitive is array descriptor" {
|
||||
defer arena.deinit();
|
||||
const aa = arena.allocator();
|
||||
|
||||
const s32 = try makeTypeExpr(aa, "s32");
|
||||
const slice = try makeSlice(aa, s32);
|
||||
const i32_te = try makeTypeExpr(aa, "i32");
|
||||
const slice = try makeSlice(aa, i32_te);
|
||||
|
||||
var buf: std.ArrayList(u8) = .empty;
|
||||
defer buf.deinit(a);
|
||||
@@ -181,11 +181,11 @@ test "deriveMethod respects #jni_method_descriptor override verbatim" {
|
||||
defer arena.deinit();
|
||||
const aa = arena.allocator();
|
||||
|
||||
// The actual sx signature `(self: *Self) -> s32` would derive to
|
||||
// The actual sx signature `(self: *Self) -> i32` would derive to
|
||||
// `()I`. The override should win regardless.
|
||||
const self_te = try makeTypeExpr(aa, "Self");
|
||||
const self_ptr = try makePointer(aa, self_te);
|
||||
const ret = try makeTypeExpr(aa, "s32");
|
||||
const ret = try makeTypeExpr(aa, "i32");
|
||||
|
||||
const method: ast.ForeignMethodDecl = .{
|
||||
.name = "weirdMethod",
|
||||
@@ -266,10 +266,10 @@ test "deriveMethod skips implicit self for instance methods" {
|
||||
defer arena.deinit();
|
||||
const aa = arena.allocator();
|
||||
|
||||
// method: getId :: (self: *Self) -> s32 → ()I
|
||||
// method: getId :: (self: *Self) -> i32 → ()I
|
||||
const self_te = try makeTypeExpr(aa, "Self");
|
||||
const self_ptr = try makePointer(aa, self_te);
|
||||
const ret = try makeTypeExpr(aa, "s32");
|
||||
const ret = try makeTypeExpr(aa, "i32");
|
||||
|
||||
const method: ast.ForeignMethodDecl = .{
|
||||
.name = "getId",
|
||||
@@ -289,9 +289,9 @@ test "deriveMethod for static method emits all params" {
|
||||
defer arena.deinit();
|
||||
const aa = arena.allocator();
|
||||
|
||||
// static abs :: (n: s32) -> s32 → (I)I
|
||||
const n_ty = try makeTypeExpr(aa, "s32");
|
||||
const ret = try makeTypeExpr(aa, "s32");
|
||||
// static abs :: (n: i32) -> i32 → (I)I
|
||||
const n_ty = try makeTypeExpr(aa, "i32");
|
||||
const ret = try makeTypeExpr(aa, "i32");
|
||||
|
||||
const method: ast.ForeignMethodDecl = .{
|
||||
.name = "abs",
|
||||
@@ -311,10 +311,10 @@ test "deriveMethod with multiple primitive params and void return" {
|
||||
defer arena.deinit();
|
||||
const aa = arena.allocator();
|
||||
|
||||
// setBounds :: (self: *Self, x: s32, y: s32, w: s32, h: s32) -> void → (IIII)V
|
||||
// setBounds :: (self: *Self, x: i32, y: i32, w: i32, h: i32) -> void → (IIII)V
|
||||
const self_te = try makeTypeExpr(aa, "Self");
|
||||
const self_ptr = try makePointer(aa, self_te);
|
||||
const s = try makeTypeExpr(aa, "s32");
|
||||
const s = try makeTypeExpr(aa, "i32");
|
||||
|
||||
const method: ast.ForeignMethodDecl = .{
|
||||
.name = "setBounds",
|
||||
@@ -334,12 +334,12 @@ test "deriveMethod with slice param" {
|
||||
defer arena.deinit();
|
||||
const aa = arena.allocator();
|
||||
|
||||
// copy :: (self: *Self, src: []s8) -> s32 → ([B)I
|
||||
// copy :: (self: *Self, src: []i8) -> i32 → ([B)I
|
||||
const self_te = try makeTypeExpr(aa, "Self");
|
||||
const self_ptr = try makePointer(aa, self_te);
|
||||
const s8 = try makeTypeExpr(aa, "s8");
|
||||
const src_slice = try makeSlice(aa, s8);
|
||||
const ret = try makeTypeExpr(aa, "s32");
|
||||
const i8_te = try makeTypeExpr(aa, "i8");
|
||||
const src_slice = try makeSlice(aa, i8_te);
|
||||
const ret = try makeTypeExpr(aa, "i32");
|
||||
|
||||
const method: ast.ForeignMethodDecl = .{
|
||||
.name = "copy",
|
||||
@@ -380,13 +380,13 @@ test "isJniReturnTypeSupported accepts the dispatchable set + pointers only" {
|
||||
const t = &table;
|
||||
|
||||
// The Call<T>Method-dispatchable primitives.
|
||||
inline for (.{ types.TypeId.void, types.TypeId.bool, types.TypeId.s32, types.TypeId.s64, types.TypeId.f32, types.TypeId.f64 }) |ty| {
|
||||
inline for (.{ types.TypeId.void, types.TypeId.bool, types.TypeId.i32, types.TypeId.i64, types.TypeId.f32, types.TypeId.f64 }) |ty| {
|
||||
try std.testing.expect(desc.isJniReturnTypeSupported(t, ty));
|
||||
}
|
||||
|
||||
// Other primitive widths are NOT dispatchable (would hit emit_llvm's
|
||||
// undef-producing `else` arm — the footgun this predicate guards).
|
||||
inline for (.{ types.TypeId.s8, types.TypeId.s16, types.TypeId.u8, types.TypeId.u32, types.TypeId.u64 }) |ty| {
|
||||
inline for (.{ types.TypeId.i8, types.TypeId.i16, types.TypeId.u8, types.TypeId.u32, types.TypeId.u64 }) |ty| {
|
||||
try std.testing.expect(!desc.isJniReturnTypeSupported(t, ty));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
//
|
||||
// void → V
|
||||
// bool → Z (jboolean)
|
||||
// s8 → B (jbyte)
|
||||
// s16 → S (jshort)
|
||||
// s32 → I (jint)
|
||||
// s64 → J (jlong)
|
||||
// i8 → B (jbyte)
|
||||
// i16 → S (jshort)
|
||||
// i32 → I (jint)
|
||||
// i64 → J (jlong)
|
||||
// u8 → B (jbyte — JNI bytes are signed; sx u8 still bridges via B)
|
||||
// u16 → C (jchar — unsigned 16-bit)
|
||||
// f32 → F (jfloat)
|
||||
@@ -137,12 +137,12 @@ fn primitiveChar(name: []const u8) ?u8 {
|
||||
const table = [_]struct { name: []const u8, ch: u8 }{
|
||||
.{ .name = "void", .ch = 'V' },
|
||||
.{ .name = "bool", .ch = 'Z' },
|
||||
.{ .name = "s8", .ch = 'B' },
|
||||
.{ .name = "i8", .ch = 'B' },
|
||||
.{ .name = "u8", .ch = 'B' },
|
||||
.{ .name = "s16", .ch = 'S' },
|
||||
.{ .name = "i16", .ch = 'S' },
|
||||
.{ .name = "u16", .ch = 'C' },
|
||||
.{ .name = "s32", .ch = 'I' },
|
||||
.{ .name = "s64", .ch = 'J' },
|
||||
.{ .name = "i32", .ch = 'I' },
|
||||
.{ .name = "i64", .ch = 'J' },
|
||||
.{ .name = "f32", .ch = 'F' },
|
||||
.{ .name = "f64", .ch = 'D' },
|
||||
};
|
||||
@@ -160,7 +160,7 @@ fn primitiveChar(name: []const u8) ?u8 {
|
||||
/// Pointer-typed returns route through `CallObjectMethod`.
|
||||
pub fn isJniReturnTypeSupported(table: *const types.TypeTable, ret_ty: TypeId) bool {
|
||||
return switch (ret_ty) {
|
||||
.void, .bool, .s32, .s64, .f32, .f64 => true,
|
||||
.void, .bool, .i32, .i64, .f32, .f64 => true,
|
||||
else => blk: {
|
||||
if (ret_ty.isBuiltin()) break :blk false;
|
||||
const info = table.get(ret_ty);
|
||||
|
||||
@@ -276,7 +276,7 @@ test "field declarations render as private Java fields" {
|
||||
const aa = arena.allocator();
|
||||
|
||||
const surface_view_ty = try makePointer(aa, try makeTypeExpr(aa, "SurfaceView"));
|
||||
const int_ty = try makeTypeExpr(aa, "s32");
|
||||
const int_ty = try makeTypeExpr(aa, "i32");
|
||||
const self_ty = try makePointer(aa, try makeTypeExpr(aa, "Self"));
|
||||
const body = try makeBodyMarker(aa);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
// to bind the `sx_<method>` symbols.
|
||||
//
|
||||
// Type matrix covered today:
|
||||
// - void return + primitive returns (s8/s16/s32/s64, u8/u16, bool,
|
||||
// - void return + primitive returns (i8/i16/i32/i64, u8/u16, bool,
|
||||
// f32/f64)
|
||||
// - `(self: *Self)` plus primitive params
|
||||
// - cross-class refs (`*Foo` where Foo is another declared
|
||||
@@ -341,12 +341,12 @@ fn emitJavaType(
|
||||
fn javaPrimitiveName(name: []const u8) ?[]const u8 {
|
||||
if (std.mem.eql(u8, name, "void")) return "void";
|
||||
if (std.mem.eql(u8, name, "bool")) return "boolean";
|
||||
if (std.mem.eql(u8, name, "s8")) return "byte";
|
||||
if (std.mem.eql(u8, name, "i8")) return "byte";
|
||||
if (std.mem.eql(u8, name, "u8")) return "byte";
|
||||
if (std.mem.eql(u8, name, "s16")) return "short";
|
||||
if (std.mem.eql(u8, name, "i16")) return "short";
|
||||
if (std.mem.eql(u8, name, "u16")) return "char";
|
||||
if (std.mem.eql(u8, name, "s32")) return "int";
|
||||
if (std.mem.eql(u8, name, "s64")) return "long";
|
||||
if (std.mem.eql(u8, name, "i32")) return "int";
|
||||
if (std.mem.eql(u8, name, "i64")) return "long";
|
||||
if (std.mem.eql(u8, name, "f32")) return "float";
|
||||
if (std.mem.eql(u8, name, "f64")) return "double";
|
||||
return null;
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -556,7 +556,7 @@ pub const Lowering = struct {
|
||||
return self.resolveTypeWithBindings(rt);
|
||||
}
|
||||
// No explicit annotation — the type is inferred from the body, which
|
||||
// references the function's own parameters (`(x: s32) => x * 2`). Those
|
||||
// references the function's own parameters (`(x: i32) => x * 2`). Those
|
||||
// params aren't pushed into `self.scope` until body lowering, so bind
|
||||
// them into a temporary scope here; otherwise `inferExprType` can't
|
||||
// resolve `x`, the inference yields `.unresolved`, and that reaches LLVM
|
||||
@@ -806,7 +806,7 @@ pub const Lowering = struct {
|
||||
// trampoline bodies (`(*void, $args[0]) -> $args[1]`) in stdlib's
|
||||
// generic Into(Block) impl. OOB indices / a missing binding emit a
|
||||
// diagnostic and return the `.unresolved` sentinel — never a plausible
|
||||
// `.s64`, which would silently fabricate an 8-byte int.
|
||||
// `.i64`, which would silently fabricate an 8-byte int.
|
||||
if (node.data == .pack_index_type_expr) {
|
||||
const pi = node.data.pack_index_type_expr;
|
||||
if (self.pack_arg_types) |pat| {
|
||||
@@ -933,7 +933,7 @@ pub const Lowering = struct {
|
||||
},
|
||||
.identifier => |id| return self.resolveNominalLeaf(id.name, id.is_raw, node.span),
|
||||
// A non-spread tuple literal in a type position is a tuple-type
|
||||
// literal (`(s32, s32)`); validate its elements are types and reject
|
||||
// literal (`(i32, i32)`); validate its elements are types and reject
|
||||
// non-type elements loudly.
|
||||
.tuple_literal => return self.resolveTupleLiteralTypeArg(node),
|
||||
else => return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map),
|
||||
@@ -1117,7 +1117,7 @@ pub const Lowering = struct {
|
||||
|
||||
/// Get the element type for a slice/array/string type. A non-collection
|
||||
/// type has no element type — return `.unresolved` (asking for it is a bug)
|
||||
/// rather than a plausible `.s64`.
|
||||
/// rather than a plausible `.i64`.
|
||||
pub fn getElementType(self: *Lowering, ty: TypeId) TypeId {
|
||||
if (ty == .string) return .u8;
|
||||
if (ty.isBuiltin()) return .unresolved;
|
||||
@@ -1154,7 +1154,7 @@ pub const Lowering = struct {
|
||||
/// value path (`lowerBinaryOp`) and AST-level inference
|
||||
/// (`ExprTyper.inferType`'s binary-op arm), so static typing reports
|
||||
/// exactly the type the lowered value carries. An integer LHS with a
|
||||
/// floating-point RHS promotes to the float (`s64 + f64` → `f64`); every
|
||||
/// floating-point RHS promotes to the float (`i64 + f64` → `f64`); every
|
||||
/// other pairing — including vectors / structs, whose `isInt` is false —
|
||||
/// takes the LHS type. Comparison / logical ops never reach here (they
|
||||
/// are `.bool` at both sites).
|
||||
@@ -1165,7 +1165,7 @@ pub const Lowering = struct {
|
||||
|
||||
fn isInt(ty: TypeId) bool {
|
||||
return switch (ty) {
|
||||
.s8, .s16, .s32, .s64, .u8, .u16, .u32, .u64, .usize, .isize => true,
|
||||
.i8, .i16, .i32, .i64, .u8, .u16, .u32, .u64, .usize, .isize => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
@@ -1293,15 +1293,15 @@ pub const Lowering = struct {
|
||||
var width: u8 = 0;
|
||||
var is_signed = false;
|
||||
switch (ty) {
|
||||
.s8 => {
|
||||
.i8 => {
|
||||
width = 8;
|
||||
is_signed = true;
|
||||
},
|
||||
.s16 => {
|
||||
.i16 => {
|
||||
width = 16;
|
||||
is_signed = true;
|
||||
},
|
||||
.s32 => {
|
||||
.i32 => {
|
||||
width = 32;
|
||||
is_signed = true;
|
||||
},
|
||||
@@ -1309,7 +1309,7 @@ pub const Lowering = struct {
|
||||
.u16 => width = 16,
|
||||
.u32 => width = 32,
|
||||
else => {
|
||||
if (ty.isBuiltin()) return null; // s64/u64/isize/usize/non-int
|
||||
if (ty.isBuiltin()) return null; // i64/u64/isize/usize/non-int
|
||||
switch (self.module.types.get(ty)) {
|
||||
.signed => |w| {
|
||||
width = w;
|
||||
@@ -1343,7 +1343,7 @@ pub const Lowering = struct {
|
||||
const tn = blk: {
|
||||
if (ty.isBuiltin()) break :blk self.module.types.typeName(ty);
|
||||
break :blk switch (self.module.types.get(ty)) {
|
||||
.signed => |w| std.fmt.bufPrint(&name_buf, "s{d}", .{w}) catch "integer",
|
||||
.signed => |w| std.fmt.bufPrint(&name_buf, "i{d}", .{w}) catch "integer",
|
||||
.unsigned => |w| std.fmt.bufPrint(&name_buf, "u{d}", .{w}) catch "integer",
|
||||
else => self.module.types.typeName(ty),
|
||||
};
|
||||
@@ -1449,10 +1449,10 @@ pub const Lowering = struct {
|
||||
fn typeBits(ty: TypeId) u32 {
|
||||
return switch (ty) {
|
||||
.bool => 1,
|
||||
.s8, .u8 => 8,
|
||||
.s16, .u16 => 16,
|
||||
.s32, .u32 => 32,
|
||||
.s64, .u64 => 64,
|
||||
.i8, .u8 => 8,
|
||||
.i16, .u16 => 16,
|
||||
.i32, .u32 => 32,
|
||||
.i64, .u64 => 64,
|
||||
.usize, .isize => 0, // target-dependent — use typeBitsEx
|
||||
.f32 => 32,
|
||||
.f64 => 64,
|
||||
|
||||
@@ -29,7 +29,7 @@ const hasComptimeParams = Lowering.hasComptimeParams;
|
||||
pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
var c = c_in;
|
||||
// A bare reserved-type-name spelling in call position parses as a
|
||||
// `.type_expr` (e.g. `s2(4)`), but if a function of that name is in
|
||||
// `.type_expr` (e.g. `i2(4)`), but if a function of that name is in
|
||||
// scope — a backtick-declared sx fn or a `#import c` foreign fn whose C
|
||||
// name collides with a reserved type spelling — it is a CALL to that
|
||||
// function. `TypeName(val)` is not a cast (casts are `cast(T, val)`), so
|
||||
@@ -411,7 +411,7 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
// Check builtins first (these are handled natively by interpreter and emitter)
|
||||
if (resolveBuiltin(id.name)) |bid| {
|
||||
const ret_ty: TypeId = switch (bid) {
|
||||
.size_of, .align_of => .s64,
|
||||
.size_of, .align_of => .i64,
|
||||
.sqrt, .sin, .cos, .floor => blk: {
|
||||
// Math builtins: return type matches argument type ($T -> T)
|
||||
if (c.args.len > 0) {
|
||||
@@ -530,8 +530,8 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
const callee_ref = if (binding.is_alloca) self.builder.load(binding.ref, binding.ty) else binding.ref;
|
||||
const ret_ty = if (!binding.ty.isBuiltin()) blk: {
|
||||
const bti = self.module.types.get(binding.ty);
|
||||
break :blk if (bti == .function) bti.function.ret else .s64;
|
||||
} else .s64;
|
||||
break :blk if (bti == .function) bti.function.ret else .i64;
|
||||
} else .i64;
|
||||
var final_args = std.ArrayList(Ref).empty;
|
||||
defer final_args.deinit(self.alloc);
|
||||
if (self.fnPtrTypeWantsCtx(binding.ty)) {
|
||||
@@ -623,8 +623,8 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
// Type constructor call: Sx(f32).user(0.5) — obj is a call that returns a type
|
||||
if (fa.object.data == .call) {
|
||||
const inner_call = &fa.object.data.call;
|
||||
// Generic struct STATIC-METHOD head (`Box(s64).make(..)` or the
|
||||
// qualified `a.Box(s64).make(..)`): the layout author is chosen
|
||||
// Generic struct STATIC-METHOD head (`Box(i64).make(..)` or the
|
||||
// qualified `a.Box(i64).make(..)`): the layout author is chosen
|
||||
// by the single head choke-point (CP-1) and the method body by
|
||||
// the instance's STAMPED author (CP-4), so layout-author ≡
|
||||
// body-author for BOTH bare and qualified heads (E4 #1 / #2).
|
||||
@@ -1025,7 +1025,7 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
// call PLAN selected for the receiver's source — the SAME author
|
||||
// plan typed the call's result as, so dispatch and typing can't
|
||||
// disagree (without this, a string-typed winner over
|
||||
// an s64 shadow boxes a raw int as a string pointer → segfault).
|
||||
// an i64 shadow boxes a raw int as a string pointer → segfault).
|
||||
// The plan is the single producer; lowering consumes its verdict
|
||||
// (`sel_author` / `cplan.ambiguous_collision`, computed once above)
|
||||
// rather than re-resolving the field name. `.ambiguous` → loud
|
||||
@@ -1189,7 +1189,7 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
// Indirect call through expression
|
||||
const callee_ref = self.lowerExpr(c.callee);
|
||||
const owned = self.alloc.dupe(Ref, args.items) catch unreachable;
|
||||
return self.builder.emit(.{ .call_indirect = .{ .callee = callee_ref, .args = owned } }, .s64);
|
||||
return self.builder.emit(.{ .call_indirect = .{ .callee = callee_ref, .args = owned } }, .i64);
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1426,7 +1426,7 @@ pub fn lowerRuntimeDispatchCall(
|
||||
const type_tag_raw = self.lowerExpr(type_tag_node orelse return self.emitError("dispatch", call_node.callee.span));
|
||||
const type_tag_node_ty = self.inferExprType(type_tag_node.?);
|
||||
const type_tag = if (type_tag_node_ty == .any)
|
||||
self.builder.emit(.{ .unbox_any = .{ .operand = type_tag_raw } }, .s64)
|
||||
self.builder.emit(.{ .unbox_any = .{ .operand = type_tag_raw } }, .i64)
|
||||
else
|
||||
type_tag_raw;
|
||||
const any_val = self.lowerExpr(any_val_node orelse return self.emitError("dispatch", call_node.callee.span));
|
||||
@@ -1630,13 +1630,13 @@ pub fn lowerRuntimeDispatchCall(
|
||||
@memcpy(final_call_args[1..], call_args.items);
|
||||
}
|
||||
}
|
||||
// Coerce non-cast args (source type unknown, use s64 default).
|
||||
// Coerce non-cast args (source type unknown, use i64 default).
|
||||
// cast_arg_idx is in user-space (skips __sx_ctx); offset by ctx_slots.
|
||||
const ctx_slots: usize = if (callee_has_ctx) 1 else 0;
|
||||
for (0..@min(final_call_args.len, callee_params.len)) |ci| {
|
||||
if (ci < ctx_slots) continue; // skip __sx_ctx slot
|
||||
if ((ci - ctx_slots) != cast_arg_idx) {
|
||||
final_call_args[ci] = self.coerceToType(final_call_args[ci], .s64, callee_params[ci].ty);
|
||||
final_call_args[ci] = self.coerceToType(final_call_args[ci], .i64, callee_params[ci].ty);
|
||||
}
|
||||
}
|
||||
const result = self.builder.call(fid, final_call_args, callee_ret);
|
||||
@@ -1680,12 +1680,12 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
// size_of(T) → const_int(sizeof(T))
|
||||
const ty = self.resolveTypeArg(c.args[0]);
|
||||
const size: i64 = @intCast(self.typeSizeBytes(ty));
|
||||
return self.builder.constInt(size, .s64);
|
||||
return self.builder.constInt(size, .i64);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "align_of")) {
|
||||
const ty = self.resolveTypeArg(c.args[0]);
|
||||
const a: i64 = @intCast(self.module.types.typeAlignBytes(ty));
|
||||
return self.builder.constInt(a, .s64);
|
||||
return self.builder.constInt(a, .i64);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "field_count")) {
|
||||
// field_count(T) → const_int(N)
|
||||
@@ -1700,7 +1700,7 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
.vector => |v| @intCast(v.length),
|
||||
else => 0,
|
||||
};
|
||||
return self.builder.constInt(count, .s64);
|
||||
return self.builder.constInt(count, .i64);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "type_name")) {
|
||||
// type_name(T):
|
||||
@@ -1712,8 +1712,8 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
// `callBuiltin(.type_name, [arg_ref])`. The interp's
|
||||
// arm (commit 9600ba5) reads the runtime `.type_tag`
|
||||
// and returns the per-position name. Without this
|
||||
// split, the catch-all `else => .s64` in
|
||||
// `resolveTypeArg` silently returns "s64" for every
|
||||
// split, the catch-all `else => .i64` in
|
||||
// `resolveTypeArg` silently returns "i64" for every
|
||||
// dynamic call — exactly the silent-arm pattern the
|
||||
// project's REJECTED PATTERNS forbid.
|
||||
if (self.isStaticTypeArg(c.args[0])) {
|
||||
@@ -1729,8 +1729,8 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
if (std.mem.eql(u8, name, "type_eq")) {
|
||||
// type_eq(T1, T2) → const_bool — comptime TypeId equality.
|
||||
// TypeIds are interned per structural shape so equality on
|
||||
// them matches the user's intuition: `type_eq(s64, s64)` is
|
||||
// true, `type_eq(*s64, *s64)` is true, distinct shapes are
|
||||
// them matches the user's intuition: `type_eq(i64, i64)` is
|
||||
// true, `type_eq(*i64, *i64)` is true, distinct shapes are
|
||||
// false. Pack-indexed types (`$args[0]`) resolve through
|
||||
// `resolveTypeArg` → `resolveTypeWithBindings`.
|
||||
if (c.args.len < 2) return self.builder.constBool(false);
|
||||
@@ -1745,7 +1745,7 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
// `any_to_string` — emits a `callBuiltin`: the interp reads
|
||||
// the boxed TypeId, LLVM GEPs a per-type signedness table.
|
||||
// Mirrors `type_name`'s static/dynamic split; the same split
|
||||
// avoids `resolveTypeArg`'s silent `.s64` default lying about
|
||||
// avoids `resolveTypeArg`'s silent `.i64` default lying about
|
||||
// a runtime Type value.
|
||||
if (c.args.len < 1) return self.builder.constBool(false);
|
||||
if (self.isStaticTypeArg(c.args[0])) {
|
||||
@@ -1873,7 +1873,7 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
// the returned value carries Type semantics (tag field
|
||||
// says ".any" → the value field holds the type id).
|
||||
const val = self.lowerExpr(c.args[0]);
|
||||
const tag_val = self.builder.structGet(val, 0, .s64);
|
||||
const tag_val = self.builder.structGet(val, 0, .i64);
|
||||
return self.builder.boxAny(tag_val, .any);
|
||||
} else {
|
||||
return self.builder.constType(arg_ty);
|
||||
@@ -1881,14 +1881,14 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
}
|
||||
if (std.mem.eql(u8, name, "field_index")) {
|
||||
// field_index(T, val) → extract tag from tagged union
|
||||
if (c.args.len < 2) return self.builder.constInt(0, .s64);
|
||||
if (c.args.len < 2) return self.builder.constInt(0, .i64);
|
||||
const val = self.lowerExpr(c.args[1]);
|
||||
// For tagged unions: extract field 0 (the tag)
|
||||
return self.builder.emit(.{ .enum_tag = .{ .operand = val } }, .s64);
|
||||
return self.builder.emit(.{ .enum_tag = .{ .operand = val } }, .i64);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "field_value_int")) {
|
||||
// field_value_int(T, i) → lookup enum variant value by index
|
||||
if (c.args.len < 2) return self.builder.constInt(0, .s64);
|
||||
if (c.args.len < 2) return self.builder.constInt(0, .i64);
|
||||
const ty = self.resolveTypeArg(c.args[0]);
|
||||
const idx = self.lowerExpr(c.args[1]);
|
||||
// For enums with explicit values, build a global value array and index into it
|
||||
@@ -1901,11 +1901,11 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
var elems = std.ArrayList(Ref).empty;
|
||||
defer elems.deinit(self.alloc);
|
||||
for (vals) |v| {
|
||||
elems.append(self.alloc, self.builder.constInt(v, .s64)) catch unreachable;
|
||||
elems.append(self.alloc, self.builder.constInt(v, .i64)) catch unreachable;
|
||||
}
|
||||
const arr_ty = self.module.types.arrayOf(.s64, @intCast(vals.len));
|
||||
const arr_ty = self.module.types.arrayOf(.i64, @intCast(vals.len));
|
||||
const arr = self.builder.structInit(elems.items, arr_ty);
|
||||
return self.builder.emit(.{ .index_get = .{ .lhs = arr, .rhs = idx } }, .s64);
|
||||
return self.builder.emit(.{ .index_get = .{ .lhs = arr, .rhs = idx } }, .i64);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1921,7 +1921,7 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
/// shapes), or a runtime `Type` value — which is `.any`-typed at
|
||||
/// runtime (`type_of(x)`, a `[]Type` element `list[i]`, a `Type`-typed
|
||||
/// local / field / param). Any other expression — a value of type
|
||||
/// s64 / f64 / bool / a struct — is NOT a type.
|
||||
/// i64 / f64 / bool / a struct — is NOT a type.
|
||||
pub fn reflectionArgIsType(self: *Lowering, arg: *const Node) bool {
|
||||
if (self.isStaticTypeArg(arg)) return true;
|
||||
return self.inferExprType(arg) == .any;
|
||||
@@ -1986,7 +1986,7 @@ pub fn reflectionErrorSentinel(self: *Lowering, name: []const u8) Ref {
|
||||
std.mem.eql(u8, name, "type_is_unsigned") or
|
||||
std.mem.eql(u8, name, "is_flags"))
|
||||
return self.builder.constBool(false);
|
||||
return self.builder.constInt(0, .s64);
|
||||
return self.builder.constInt(0, .i64);
|
||||
}
|
||||
|
||||
/// After args have been lowered, append the lowered values of any
|
||||
|
||||
@@ -214,7 +214,7 @@ pub fn lowerLambda(self: *Lowering, lam: *const ast.Lambda) Ref {
|
||||
const env_local = self.builder.alloca(env_struct_ty);
|
||||
// Compute env size
|
||||
const env_byte_size_inner = self.computeEnvSize(capture_list);
|
||||
const env_size_val = self.builder.constInt(@intCast(env_byte_size_inner), .s64);
|
||||
const env_size_val = self.builder.constInt(@intCast(env_byte_size_inner), .i64);
|
||||
// memcpy(local_alloca, env_param, size)
|
||||
_ = self.callForeign("memcpy", &.{ env_local, env_param_ref, env_size_val }, self.module.types.ptrTo(.void));
|
||||
|
||||
@@ -247,7 +247,7 @@ pub fn lowerLambda(self: *Lowering, lam: *const ast.Lambda) Ref {
|
||||
// Bind params (user args start at user_param_base_lam, shifted past ctx + env).
|
||||
// Use the signature types computed above (`params`), which already
|
||||
// applied contextual typing from the target closure to untyped params —
|
||||
// `resolveParamType` alone would drop it and default each to s64.
|
||||
// `resolveParamType` alone would drop it and default each to i64.
|
||||
for (lam.params, 0..) |p, i| {
|
||||
const pty = params.items[user_param_base + i].ty;
|
||||
const slot = self.builder.alloca(pty);
|
||||
@@ -355,7 +355,7 @@ pub fn lowerLambda(self: *Lowering, lam: *const ast.Lambda) Ref {
|
||||
// `push Context.{ allocator = ... }` and a tracker / arena
|
||||
// counts the env allocation alongside everything else.
|
||||
const env_byte_size = self.computeEnvSize(capture_list);
|
||||
const env_size = self.builder.constInt(@intCast(env_byte_size), .s64);
|
||||
const env_size = self.builder.constInt(@intCast(env_byte_size), .i64);
|
||||
const ptr_void = self.module.types.ptrTo(.void);
|
||||
const env_heap = self.allocViaContext(env_size, ptr_void);
|
||||
// memcpy(heap, stack_alloca, size)
|
||||
|
||||
@@ -24,7 +24,7 @@ pub fn lowerXX(self: *Lowering, operand: Ref, operand_node: *const Node) Ref {
|
||||
// Use the operand's *actual* lowered Ref type rather than reaching
|
||||
// back through inferExprType — the latter doesn't cover every
|
||||
// expression shape (notably lambdas), and a wrong src_ty here can
|
||||
// route the cast through coerceToType (e.g. a bogus s64→ptr bitcast)
|
||||
// route the cast through coerceToType (e.g. a bogus i64→ptr bitcast)
|
||||
// and silently skip the user-space Into fallback.
|
||||
const src_ty = self.builder.getRefType(operand);
|
||||
const target_explicit = self.target_type != null;
|
||||
@@ -83,7 +83,7 @@ pub fn lowerXX(self: *Lowering, operand: Ref, operand_node: *const Node) Ref {
|
||||
const result = self.coerceExplicit(operand, src_ty, dst_ty);
|
||||
|
||||
// User-space fallback via `impl Into(Target) for Source`. Only fires
|
||||
// when the target was explicitly named (not the .s64 default), src and
|
||||
// when the target was explicitly named (not the .i64 default), src and
|
||||
// dst differ, and the built-in ladder made no progress. Built-ins
|
||||
// always win.
|
||||
if (target_explicit and src_ty != dst_ty and result == operand) {
|
||||
@@ -445,14 +445,14 @@ pub fn lowerAnyToF64Dispatch(self: *Lowering, any_val: Ref) Ref {
|
||||
const result_slot = self.builder.alloca(.f64);
|
||||
|
||||
// Extract type tag from Any
|
||||
const tag = self.builder.structGet(any_val, 0, .s64);
|
||||
const tag = self.builder.structGet(any_val, 0, .i64);
|
||||
|
||||
const f32_bb = self.freshBlock("f32.unbox");
|
||||
const f64_bb = self.freshBlock("f64.unbox");
|
||||
const merge_bb = self.freshBlock("float.merge");
|
||||
|
||||
// Branch: tag == f32_tag ? f32_bb : f64_bb
|
||||
const f32_tag = self.builder.constInt(TypeId.f32.index(), .s64);
|
||||
const f32_tag = self.builder.constInt(TypeId.f32.index(), .i64);
|
||||
const cond = self.builder.emit(.{ .cmp_eq = .{ .lhs = tag, .rhs = f32_tag } }, .bool);
|
||||
self.builder.condBr(cond, f32_bb, &.{}, f64_bb, &.{});
|
||||
|
||||
@@ -479,7 +479,7 @@ pub fn lowerAnyToF64Dispatch(self: *Lowering, any_val: Ref) Ref {
|
||||
}
|
||||
|
||||
/// Produce a default value for a type, applying struct field defaults.
|
||||
/// For structs with defaults (e.g., `b: s32 = 99`), creates a struct_literal with defaults applied.
|
||||
/// For structs with defaults (e.g., `b: i32 = 99`), creates a struct_literal with defaults applied.
|
||||
/// For other types, returns a zero value.
|
||||
pub fn buildDefaultValue(self: *Lowering, ty: TypeId) Ref {
|
||||
if (ty.isBuiltin()) return self.builder.constInt(0, ty);
|
||||
@@ -534,7 +534,7 @@ pub fn zeroValue(self: *Lowering, ty: TypeId) Ref {
|
||||
if (ty.isBuiltin()) return self.builder.constInt(0, ty);
|
||||
const info = self.module.types.get(ty);
|
||||
return switch (info) {
|
||||
// Arbitrary-width integer types (u1, u2, s4, ...) interned as
|
||||
// Arbitrary-width integer types (u1, u2, i4, ...) interned as
|
||||
// `.signed`/`.unsigned` variants — fall through `isBuiltin()`.
|
||||
.signed, .unsigned => self.builder.constInt(0, ty),
|
||||
.pointer, .tuple, .optional => self.builder.constNull(ty),
|
||||
@@ -610,8 +610,8 @@ pub fn coerceMode(self: *Lowering, val: Ref, src_ty: TypeId, dst_ty: TypeId, mod
|
||||
}
|
||||
return val;
|
||||
},
|
||||
// Tuple → Tuple element-wise coercion (e.g. a `(s64, s64)` literal
|
||||
// flowing into a `(s32, s32)` slot — the multi-value failable success
|
||||
// Tuple → Tuple element-wise coercion (e.g. a `(i64, i64)` literal
|
||||
// flowing into a `(i32, i32)` slot — the multi-value failable success
|
||||
// tuple). Same arity: extract each slot, coerce it, rebuild.
|
||||
.tuple_elementwise => {
|
||||
const si = self.module.types.get(src_ty);
|
||||
@@ -693,14 +693,14 @@ pub fn coerceMode(self: *Lowering, val: Ref, src_ty: TypeId, dst_ty: TypeId, mod
|
||||
}
|
||||
|
||||
/// Apply C default argument promotion to variadic-tail args. These rules
|
||||
/// (bool/s8/s16/u8/u16 → s32, f32 → f64) match the C calling convention's
|
||||
/// (bool/i8/i16/u8/u16 → i32, f32 → f64) match the C calling convention's
|
||||
/// implicit promotions when an argument is passed through `...`.
|
||||
pub fn promoteCVariadicArgs(self: *Lowering, args: []Ref, fixed_count: usize) void {
|
||||
if (args.len <= fixed_count) return;
|
||||
for (args[fixed_count..]) |*arg| {
|
||||
const src_ty = self.builder.getRefType(arg.*);
|
||||
const promoted: TypeId = switch (src_ty) {
|
||||
.bool, .s8, .s16, .u8, .u16 => .s32,
|
||||
.bool, .i8, .i16, .u8, .u16 => .i32,
|
||||
.f32 => .f64,
|
||||
else => continue,
|
||||
};
|
||||
@@ -720,7 +720,7 @@ pub fn coerceCallArgs(self: *Lowering, args: []Ref, params: []const Function.Par
|
||||
if (src_info == .array and dst_info == .many_pointer) {
|
||||
const slot = self.builder.alloca(src_ty);
|
||||
self.builder.store(slot, args[i]);
|
||||
const zero = self.builder.constInt(0, .s64);
|
||||
const zero = self.builder.constInt(0, .i64);
|
||||
args[i] = self.builder.emit(.{ .index_gep = .{ .lhs = slot, .rhs = zero } }, dst_ty);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ pub fn evalComptimeInt(self: *Lowering, node: *const Node) ?i64 {
|
||||
pub fn lowerComptimeGlobal(self: *Lowering, name: []const u8, expr: *const Node, type_ann: ?*const Node) void {
|
||||
// When the user writes `NAME :: #run expr;` with no type annotation,
|
||||
// infer the global's type from the comptime expression's return
|
||||
// shape. `resolveType(null)` returns `.s64` for legacy reasons —
|
||||
// shape. `resolveType(null)` returns `.i64` for legacy reasons —
|
||||
// good for primitive helpers, silently wrong for anything else.
|
||||
const expr_ty = self.inferExprType(expr);
|
||||
// A failable `#run` (bare, no `catch`/`or`): the comptime function
|
||||
@@ -1130,7 +1130,7 @@ pub fn foldComptimeFloatInit(self: *Lowering, node: *const Node, dst: TypeId) ?R
|
||||
// `evalConstFloatExpr` only succeeds for literal / const-arithmetic
|
||||
// nodes, never an unbound pack index. `inferExprType` is the primary
|
||||
// signal, but it reads a const's DECLARED type — which is a placeholder
|
||||
// `s64` for an untyped float-EXPRESSION const (`ME :: 4.0 + 1.0`), so
|
||||
// `i64` for an untyped float-EXPRESSION const (`ME :: 4.0 + 1.0`), so
|
||||
// `ME / 2` would look like integer division; `isFloatValuedExpr` (judging
|
||||
// by VALUE) catches that case so it narrows under the unified rule too.
|
||||
if (!isFloat(self.inferExprType(node)) and !program_index_mod.isFloatValuedExpr(node, self)) return null;
|
||||
|
||||
@@ -61,7 +61,7 @@ pub fn lowerIfExpr(self: *Lowering, ie: *const ast.IfExpr) Ref {
|
||||
|
||||
// Optional binding: `if val := expr { ... }`
|
||||
// Clear target_type so the ternary's result type doesn't leak into the condition
|
||||
// (e.g., `if x != 0 then 1.0 else 2.0` — the `0` must be s64, not f32)
|
||||
// (e.g., `if x != 0 then 1.0 else 2.0` — the `0` must be i64, not f32)
|
||||
const saved_cond_target = self.target_type;
|
||||
self.target_type = null;
|
||||
const opt_val = self.lowerExpr(ie.condition);
|
||||
@@ -273,12 +273,12 @@ pub fn listView(self: *Lowering, value: Ref, ty: TypeId) ?struct { data: Ref, da
|
||||
return .{
|
||||
.data = self.builder.emit(.{ .struct_get = .{ .base = value, .field_index = items_idx.? } }, items_ty),
|
||||
.data_ty = items_ty,
|
||||
.len = self.builder.emit(.{ .struct_get = .{ .base = value, .field_index = len_idx.? } }, .s64),
|
||||
.len = self.builder.emit(.{ .struct_get = .{ .base = value, .field_index = len_idx.? } }, .i64),
|
||||
};
|
||||
}
|
||||
|
||||
/// Lowered prep for one position of a multi-iterable `for` header. Every
|
||||
/// position gets its own s64 cursor slot (ranges start at their `start`,
|
||||
/// position gets its own i64 cursor slot (ranges start at their `start`,
|
||||
/// collections at 0); all cursors advance by 1 per iteration, and ONLY the
|
||||
/// first position's bound terminates the loop (first-iterable-wins).
|
||||
const IterPrep = struct {
|
||||
@@ -316,13 +316,13 @@ pub fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
for (fe.iterables, 0..) |it, i| {
|
||||
if (it.is_range) {
|
||||
var start_ref = self.lowerExpr(it.expr);
|
||||
if (it.start_exclusive) start_ref = self.builder.add(start_ref, self.builder.constInt(1, .s64), .s64);
|
||||
const slot = self.builder.alloca(.s64);
|
||||
if (it.start_exclusive) start_ref = self.builder.add(start_ref, self.builder.constInt(1, .i64), .i64);
|
||||
const slot = self.builder.alloca(.i64);
|
||||
self.builder.store(slot, start_ref);
|
||||
if (i == 0) {
|
||||
// Parser guarantees the first iterable is bounded.
|
||||
var end_ref = self.lowerExpr(it.range_end.?);
|
||||
if (it.end_inclusive) end_ref = self.builder.add(end_ref, self.builder.constInt(1, .s64), .s64);
|
||||
if (it.end_inclusive) end_ref = self.builder.add(end_ref, self.builder.constInt(1, .i64), .i64);
|
||||
limit = end_ref;
|
||||
}
|
||||
preps.append(self.alloc, .{ .is_range = true, .slot = slot }) catch unreachable;
|
||||
@@ -349,7 +349,7 @@ pub fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
data_ty = lv.data_ty;
|
||||
len = lv.len;
|
||||
} else if (i == 0) {
|
||||
len = self.builder.emit(.{ .length = .{ .operand = data } }, .s64);
|
||||
len = self.builder.emit(.{ .length = .{ .operand = data } }, .i64);
|
||||
}
|
||||
|
||||
const elem_ty = self.getElementType(data_ty);
|
||||
@@ -367,8 +367,8 @@ pub fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
}
|
||||
const is_array = !data_ty.isBuiltin() and self.module.types.get(data_ty) == .array;
|
||||
const storage = if (is_array and !was_deref) self.getExprAlloca(it.expr) else null;
|
||||
const slot = self.builder.alloca(.s64);
|
||||
self.builder.store(slot, self.builder.constInt(0, .s64));
|
||||
const slot = self.builder.alloca(.i64);
|
||||
self.builder.store(slot, self.builder.constInt(0, .i64));
|
||||
if (i == 0) limit = len;
|
||||
preps.append(self.alloc, .{
|
||||
.is_range = false,
|
||||
@@ -391,7 +391,7 @@ pub fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
|
||||
// Header: first cursor against the first bound.
|
||||
self.builder.switchToBlock(header_bb);
|
||||
const cur0 = self.builder.load(preps.items[0].slot, .s64);
|
||||
const cur0 = self.builder.load(preps.items[0].slot, .i64);
|
||||
const cmp = self.builder.cmpLt(cur0, limit);
|
||||
self.builder.condBr(cmp, body_bb, &.{}, exit_bb, &.{});
|
||||
|
||||
@@ -404,9 +404,9 @@ pub fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
|
||||
for (fe.captures, 0..) |cap, i| {
|
||||
const prep = preps.items[i];
|
||||
const cur = if (i == 0) cur0 else self.builder.load(prep.slot, .s64);
|
||||
const cur = if (i == 0) cur0 else self.builder.load(prep.slot, .i64);
|
||||
if (prep.is_range) {
|
||||
body_scope.put(cap.name, .{ .ref = cur, .ty = .s64, .is_alloca = false });
|
||||
body_scope.put(cap.name, .{ .ref = cur, .ty = .i64, .is_alloca = false });
|
||||
continue;
|
||||
}
|
||||
const bind_ty = if (cap.by_ref) self.module.types.ptrTo(prep.elem_ty) else prep.elem_ty;
|
||||
@@ -453,10 +453,10 @@ pub fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
// Increment block: advance every cursor and jump back to header.
|
||||
self.builder.switchToBlock(inc_bb);
|
||||
{
|
||||
const one = self.builder.constInt(1, .s64);
|
||||
const one = self.builder.constInt(1, .i64);
|
||||
for (preps.items) |prep| {
|
||||
const cur = self.builder.load(prep.slot, .s64);
|
||||
const next = self.builder.add(cur, one, .s64);
|
||||
const cur = self.builder.load(prep.slot, .i64);
|
||||
const next = self.builder.add(cur, one, .i64);
|
||||
self.builder.store(prep.slot, next);
|
||||
}
|
||||
self.builder.br(header_bb, &.{});
|
||||
@@ -556,7 +556,7 @@ pub fn lowerInlineRangeFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
// uses like `print(i)`) and as a comptime constant (for
|
||||
// `xs[i]` substitution).
|
||||
const v = start + i;
|
||||
body_scope.put(cap.name, .{ .ref = self.builder.constInt(v, .s64), .ty = .s64, .is_alloca = false });
|
||||
body_scope.put(cap.name, .{ .ref = self.builder.constInt(v, .i64), .ty = .i64, .is_alloca = false });
|
||||
var save = CursorSave{ .name = cap.name, .had_prev = false, .prev = undefined };
|
||||
if (self.comptime_constants.get(cap.name)) |p| {
|
||||
save.had_prev = true;
|
||||
@@ -796,11 +796,11 @@ pub fn lowerMatch(self: *Lowering, me: *const ast.MatchExpr) Ref {
|
||||
}
|
||||
|
||||
// Switch on the subject (for type match, subject is either a
|
||||
// bare TypeId (s64) or an Any-shaped Type value — unbox in the
|
||||
// bare TypeId (i64) or an Any-shaped Type value — unbox in the
|
||||
// latter case so the switch sees the i64 type id).
|
||||
const tag = if (is_type_match) tag_blk: {
|
||||
if (subject_ty == .any) {
|
||||
break :tag_blk self.builder.emit(.{ .unbox_any = .{ .operand = subject } }, .s64);
|
||||
break :tag_blk self.builder.emit(.{ .unbox_any = .{ .operand = subject } }, .i64);
|
||||
}
|
||||
break :tag_blk subject;
|
||||
} else if (is_optional_match) self.builder.emit(.{ .optional_has_value = .{ .operand = subject } }, .bool) else if (is_error_set_match) subject else blk: {
|
||||
@@ -810,7 +810,7 @@ pub fn lowerMatch(self: *Lowering, me: *const ast.MatchExpr) Ref {
|
||||
const ty_info = self.module.types.get(subject_ty);
|
||||
if (ty_info == .tagged_union) break :tt ty_info.tagged_union.tag_type;
|
||||
}
|
||||
break :tt .s32;
|
||||
break :tt .i32;
|
||||
};
|
||||
break :blk self.builder.enumTag(subject, tag_ty);
|
||||
};
|
||||
@@ -836,7 +836,7 @@ pub fn lowerMatch(self: *Lowering, me: *const ast.MatchExpr) Ref {
|
||||
if (is_optional_match) {
|
||||
// For optional match, unwrap the optional value
|
||||
const opt_info = self.module.types.get(subject_ty);
|
||||
const child_ty = if (opt_info == .optional) opt_info.optional.child else .s64;
|
||||
const child_ty = if (opt_info == .optional) opt_info.optional.child else .i64;
|
||||
const unwrapped = self.builder.emit(.{ .optional_unwrap = .{ .operand = subject } }, child_ty);
|
||||
arm_scope.put(capture_name, .{ .ref = unwrapped, .ty = child_ty, .is_alloca = false });
|
||||
} else {
|
||||
|
||||
@@ -268,7 +268,7 @@ pub fn injectComptimeConstants(self: *Lowering) void {
|
||||
}
|
||||
}
|
||||
|
||||
// POINTER_SIZE: s64 (4 for wasm32, 8 for wasm64 and other 64-bit targets)
|
||||
// POINTER_SIZE: i64 (4 for wasm32, 8 for wasm64 and other 64-bit targets)
|
||||
const ptr_size: i64 = if (tc.isWasm32()) 4 else 8;
|
||||
self.comptime_constants.put("POINTER_SIZE", .{ .int_val = ptr_size }) catch {};
|
||||
}
|
||||
@@ -437,7 +437,7 @@ pub fn dropModuleConst(self: *Lowering, source: ?[]const u8, name: []const u8) v
|
||||
/// Pass 1: Scan declarations — register ASTs and extern stubs, but don't lower bodies.
|
||||
pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
// Pass 0: register every numeric-literal module const (`N :: 16` and the
|
||||
// typed `N : s64 : 16`, plus float-valued `N :: 4.0` / `N : f64 : 4.0`)
|
||||
// typed `N : i64 : 16`, plus float-valued `N :: 4.0` / `N : f64 : 4.0`)
|
||||
// BEFORE any type alias is resolved below. A type alias whose dimension is
|
||||
// a named const (`Arr :: [N]T`) resolves its dimension eagerly here, on
|
||||
// the stateless registration path; that path can only read
|
||||
@@ -455,7 +455,7 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
const cd = decl.data.const_decl;
|
||||
switch (cd.value.data) {
|
||||
.int_literal => {
|
||||
const info = program_index_mod.ModuleConstInfo{ .value = cd.value, .ty = .s64 };
|
||||
const info = program_index_mod.ModuleConstInfo{ .value = cd.value, .ty = .i64 };
|
||||
self.putModuleConst(decl.source_file, cd.name, info);
|
||||
},
|
||||
.float_literal => {
|
||||
@@ -465,11 +465,11 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
// A const whose RHS is an integer EXPRESSION over other consts
|
||||
// (`M :: 2; N :: M + 1`) is itself a usable count: register it so
|
||||
// `moduleConstInt` can fold the RHS through `evalConstIntExpr`
|
||||
//. Placeholder `.s64` type — the count consumers read
|
||||
//. Placeholder `.i64` type — the count consumers read
|
||||
// only the value; if the expression doesn't fold (references a
|
||||
// non-const), `moduleConstInt` yields null and the use diagnoses.
|
||||
.binary_op, .unary_op => {
|
||||
const info = program_index_mod.ModuleConstInfo{ .value = cd.value, .ty = .s64 };
|
||||
const info = program_index_mod.ModuleConstInfo{ .value = cd.value, .ty = .i64 };
|
||||
self.putModuleConst(decl.source_file, cd.name, info);
|
||||
},
|
||||
else => {},
|
||||
@@ -565,7 +565,7 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
cd.value.data == .optional_type_expr or
|
||||
cd.value.data == .function_type_expr)
|
||||
{
|
||||
// Type alias: MyFloat :: f64; Ptr :: *u8; Cb :: (s32) -> s32;
|
||||
// Type alias: MyFloat :: f64; Ptr :: *u8; Cb :: (i32) -> i32;
|
||||
const target_ty = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
// The stateless resolver yields `.unresolved` for a shape
|
||||
// it cannot build — e.g. `Arr :: [<computed>]T`, whose
|
||||
@@ -651,7 +651,7 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
// A namespaced callee (`ns.Box(..)`) is an explicit qualified
|
||||
// reach, exempt from the bare-head visibility gate (E4).
|
||||
const head_qualified = call_data.callee.data == .field_access;
|
||||
// A qualified head `ABox :: a.Box(s64)` selects a's OWN
|
||||
// A qualified head `ABox :: a.Box(i64)` selects a's OWN
|
||||
// template via the namespace edge (mirrors the annotation
|
||||
// head site `resolveTypeCallWithBindings`), not the bare
|
||||
// last-wins `struct_template_map`.
|
||||
@@ -660,8 +660,8 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
else
|
||||
null;
|
||||
if (callee_name.len > 0) {
|
||||
// Generic-struct alias head (`ABox :: Box(s64)` /
|
||||
// `a.Box(s64)`): route layout selection through the single
|
||||
// Generic-struct alias head (`ABox :: Box(i64)` /
|
||||
// `a.Box(i64)`): route layout selection through the single
|
||||
// choke-point (CP-1); the Vector / type-fn branches stay
|
||||
// as the non-generic fall-through.
|
||||
switch (self.selectGenericStructHead(callee_name, qual_alias, head_qualified, call_data.callee.span)) {
|
||||
@@ -698,7 +698,7 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
const pt = &cd.value.data.parameterized_type_expr;
|
||||
const base_name = if (std.mem.lastIndexOfScalar(u8, pt.name, '.')) |dot| pt.name[dot + 1 ..] else pt.name;
|
||||
const pt_qualified = std.mem.indexOfScalar(u8, pt.name, '.') != null;
|
||||
// A qualified base `ABox :: a.Box(s64)` selects a's OWN
|
||||
// A qualified base `ABox :: a.Box(i64)` selects a's OWN
|
||||
// template via the namespace edge (mirrors the annotation
|
||||
// head site `resolveParameterizedWithBindings`), not the
|
||||
// bare last-wins `struct_template_map`.
|
||||
@@ -723,7 +723,7 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
}
|
||||
// comptime_expr handled in Pass 2
|
||||
|
||||
// Typed value constants (`AF_INET :s32: 2`) are registered in
|
||||
// Typed value constants (`AF_INET :i32: 2`) are registered in
|
||||
// pass 2 below — after the forward-alias fixpoint — so a
|
||||
// forward identifier alias in the annotation resolves to its
|
||||
// target instead of a fabricated stub. Untyped
|
||||
@@ -733,7 +733,7 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
// Untyped literal constants (e.g. UI_VERT_SRC :: #string GLSL...GLSL;)
|
||||
const lit_ty: ?TypeId = switch (cd.value.data) {
|
||||
.string_literal => .string,
|
||||
.int_literal => .s64,
|
||||
.int_literal => .i64,
|
||||
.float_literal => .f64,
|
||||
.bool_literal => .bool,
|
||||
// Complex constant expressions (e.g. COLOR_WHITE :: Color.{ r = 255, ... })
|
||||
@@ -825,19 +825,19 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
.own_opaque, .ambiguous, .none => false,
|
||||
};
|
||||
if (!recv_is_agg) continue;
|
||||
self.putModuleConst(decl.source_file, cd.name, .{ .value = cd.value, .ty = .s64 });
|
||||
self.putModuleConst(decl.source_file, cd.name, .{ .value = cd.value, .ty = .i64 });
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a typed module-level value constant (`AF_INET :s32: 2`). Run in
|
||||
/// Register a typed module-level value constant (`AF_INET :i32: 2`). Run in
|
||||
/// scanDecls pass 2 (after `resolveForwardIdentifierAliases`) so a forward
|
||||
/// identifier alias in the annotation (`A :: B; B :: s32; K : A : 42;`)
|
||||
/// identifier alias in the annotation (`A :: B; B :: i32; K : A : 42;`)
|
||||
/// resolves to its target rather than a fabricated empty-struct stub, which
|
||||
/// would otherwise mistype the constant.
|
||||
pub fn registerTypedModuleConst(self: *Lowering, cd: *const ast.ConstDecl) void {
|
||||
const ta = cd.type_annotation orelse return;
|
||||
// Only initializer shapes that pass 0 (binary_op / unary_op → placeholder
|
||||
// `.s64`) or the literal path register as a USABLE module const need
|
||||
// `.i64`) or the literal path register as a USABLE module const need
|
||||
// reconciling against the annotation. Every other shape (call,
|
||||
// struct/array literal, bare identifier) is never registered as a
|
||||
// foldable / emittable const, so it cannot manifest a
|
||||
@@ -860,7 +860,7 @@ pub fn registerTypedModuleConst(self: *Lowering, cd: *const ast.ConstDecl) void
|
||||
// silently-accepted const — registering it would let `emitModuleConst`
|
||||
// stamp the value with the wrong IR type (an int emitted as a `string`
|
||||
// const → a bogus pointer that segfaults at the use site) and let the
|
||||
// count path fold it (`[N]s64` → 4). Issue 0088.
|
||||
// count path fold it (`[N]i64` → 4). Issue 0088.
|
||||
if (!self.typedConstInitFits(cd.value, ty)) {
|
||||
// A non-integral compile-time float into an integer const is the
|
||||
// same implicit-narrowing failure as a typed local/field/param —
|
||||
@@ -880,7 +880,7 @@ pub fn registerTypedModuleConst(self: *Lowering, cd: *const ast.ConstDecl) void
|
||||
});
|
||||
}
|
||||
// Evict the pass-0 placeholder (`N : string : 4` and
|
||||
// `N : string : M + 2` are both pre-registered as `.s64` in scanDecls
|
||||
// `N : string : M + 2` are both pre-registered as `.i64` in scanDecls
|
||||
// pass 0); leaving it would let a count use still fold `N`.
|
||||
self.dropModuleConst(self.current_source_file, cd.name);
|
||||
return;
|
||||
@@ -904,13 +904,13 @@ pub fn registerTypedModuleConst(self: *Lowering, cd: *const ast.ConstDecl) void
|
||||
/// unsound as a compile-time literal-representability oracle here — a `null`
|
||||
/// literal's natural type is `.void`, so `classify(.void, *T)` yields `.none`
|
||||
/// and would reject the valid `P : *void : null`; `bool` is 1 bit wide, so
|
||||
/// `classify(.bool, s64)` yields `.widen` and would accept the bogus
|
||||
/// `B : s64 : true`.
|
||||
/// `classify(.bool, i64)` yields `.widen` and would accept the bogus
|
||||
/// `B : i64 : true`.
|
||||
pub fn typedConstInitFits(self: *Lowering, value: *const Node, dst_ty: TypeId) bool {
|
||||
// An INTEGER-annotated constant accepts a compile-time INTEGRAL float —
|
||||
// a literal (`K : s64 : 4.0`), an int-leaf expression (`K : s64 : M + 2.0`
|
||||
// a literal (`K : i64 : 4.0`), an int-leaf expression (`K : i64 : M + 2.0`
|
||||
// → 4), or a float-const-leaf expression whose SUM is integral
|
||||
// (`F : f64 : 2.5; K : s64 : F + 1.5` → 4). Integrality is judged on the
|
||||
// (`F : f64 : 2.5; K : i64 : F + 1.5` → 4). Integrality is judged on the
|
||||
// FLOAT fold (`evalConstFloatExpr` + `floatToIntExact`) — the SAME facility
|
||||
// the typed-local path (`foldComptimeFloatInit`) uses — not the int-only
|
||||
// folder, which folds leaf-by-leaf in `i64` and so misses an integral SUM
|
||||
@@ -969,7 +969,7 @@ pub fn constExprInitFits(self: *Lowering, init_ty: TypeId, dst_ty: TypeId) bool
|
||||
return init_ty == dst_ty;
|
||||
}
|
||||
|
||||
/// Register an array-typed `::` constant (`K : [4]s64 : .[...]`, or the
|
||||
/// Register an array-typed `::` constant (`K : [4]i64 : .[...]`, or the
|
||||
/// untyped `A :: .[1, 2, 3]`) as an IMMUTABLE module global: one storage,
|
||||
/// reads GEP it, the emitter marks it LLVMSetGlobalConstant, dead-global
|
||||
/// elimination drops it when unused. Source-aware reads come for free via
|
||||
@@ -1016,7 +1016,7 @@ pub fn registerConstArrayGlobal(self: *Lowering, cd: *const ast.ConstDecl) void
|
||||
}
|
||||
|
||||
/// Infer `[N]T` for an untyped array-literal constant. Element types unify:
|
||||
/// all ints → s64; ANY float promotes the element type to f64 (ints convert
|
||||
/// all ints → i64; ANY float promotes the element type to f64 (ints convert
|
||||
/// exactly — the int+float promotion rule for consts, element-wise); bool /
|
||||
/// string homogeneous only. A non-numeric mix or a non-inferable element
|
||||
/// shape (nested aggregate, enum literal, named const) asks for an
|
||||
@@ -1024,18 +1024,18 @@ pub fn registerConstArrayGlobal(self: *Lowering, cd: *const ast.ConstDecl) void
|
||||
pub fn inferConstArrayType(self: *Lowering, name: []const u8, elements: []const *const Node, span: ast.Span) ?TypeId {
|
||||
if (elements.len == 0) {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, span, "constant '{s}' is an empty array literal — annotate the type (e.g. `{s} : [0]s64 : .[]`)", .{ name, name });
|
||||
d.addFmt(.err, span, "constant '{s}' is an empty array literal — annotate the type (e.g. `{s} : [0]i64 : .[]`)", .{ name, name });
|
||||
return null;
|
||||
}
|
||||
var elem_ty: ?TypeId = null;
|
||||
for (elements) |e| {
|
||||
const leaf: ?TypeId = switch (e.data) {
|
||||
.int_literal => .s64,
|
||||
.int_literal => .i64,
|
||||
.float_literal => .f64,
|
||||
.bool_literal => .bool,
|
||||
.string_literal => .string,
|
||||
.unary_op => |uo| if (uo.op == .negate) switch (uo.operand.data) {
|
||||
.int_literal => .s64,
|
||||
.int_literal => .i64,
|
||||
.float_literal => .f64,
|
||||
else => null,
|
||||
} else null,
|
||||
@@ -1049,7 +1049,7 @@ pub fn inferConstArrayType(self: *Lowering, name: []const u8, elements: []const
|
||||
if (elem_ty) |prev| {
|
||||
if (prev == lt) continue;
|
||||
// Numeric mix promotes to the float element type.
|
||||
const numeric_pair = (prev == .s64 and lt == .f64) or (prev == .f64 and lt == .s64);
|
||||
const numeric_pair = (prev == .i64 and lt == .f64) or (prev == .f64 and lt == .i64);
|
||||
if (numeric_pair) {
|
||||
elem_ty = .f64;
|
||||
continue;
|
||||
@@ -1089,7 +1089,7 @@ pub fn maybeRegisterConstStructGlobal(self: *Lowering, cd: *const ast.ConstDecl)
|
||||
|
||||
/// Register a top-level mutable global (e.g., `context : Context = ---;`).
|
||||
/// Run AFTER `resolveForwardIdentifierAliases` so a forward identifier alias
|
||||
/// in the type annotation (`A :: B; B :: s32; g : A = 7;`) resolves to its
|
||||
/// in the type annotation (`A :: B; B :: i32; g : A = 7;`) resolves to its
|
||||
/// target instead of a fabricated empty-struct stub, which would otherwise
|
||||
/// give the global a type that mismatches its initializer at LLVM
|
||||
/// verification. Globals can't be named in a type position, so
|
||||
@@ -1143,7 +1143,7 @@ pub fn globalInitValue(self: *Lowering, vd: *const ast.VarDecl, var_ty: TypeId)
|
||||
self.checkIntLiteralFits(il.value, var_ty, v.span);
|
||||
break :blk .{ .int = il.value };
|
||||
},
|
||||
// A negated literal (`g : s64 = -1;`) folds through the shared
|
||||
// A negated literal (`g : i64 = -1;`) folds through the shared
|
||||
// const-expr serializer. The folded value follows the same rules as
|
||||
// the direct literal arms: int fits-check; a float at an integer
|
||||
// global narrows only when integral.
|
||||
@@ -1239,8 +1239,8 @@ pub fn diagnoseNonConstGlobal(self: *Lowering, vd: *const ast.VarDecl, v: *const
|
||||
/// is already resolved as a type author; a forward target isn't yet present,
|
||||
/// so `A` is left unregistered and its uses get falsely flagged as an unknown
|
||||
/// type. Re-resolve to a fixpoint now that every top-level name
|
||||
/// has been seen, so `A :: B; B :: s32;` converges the same as the ordered
|
||||
/// `B :: s32; A :: B;`. A value const is never an `.identifier` node
|
||||
/// has been seen, so `A :: B; B :: i32;` converges the same as the ordered
|
||||
/// `B :: i32; A :: B;`. A value const is never an `.identifier` node
|
||||
/// (`NotAType :: 123` is an int literal), and an alias whose target is a value
|
||||
/// const stays unresolved, so neither this pass nor the unknown-type suppression can register a
|
||||
/// non-type name.
|
||||
@@ -1274,8 +1274,8 @@ pub fn resolveForwardIdentifierAliases(self: *Lowering, decls: []const *const No
|
||||
if (self.aliasResolvedInSource(src, cd.name)) continue;
|
||||
const rhs = cd.value.data.identifier;
|
||||
// Pass the backtick raw flag so a forward alias whose RHS is a raw
|
||||
// identifier (`` RawAlias :: `s2 ``, target declared later) resolves
|
||||
// to the nominal `` `s2 `` author, not the builtin `s2` spelling.
|
||||
// identifier (`` RawAlias :: `i2 ``, target declared later) resolves
|
||||
// to the nominal `` `i2 `` author, not the builtin `i2` spelling.
|
||||
switch (self.selectNominalLeaf(rhs.name, src, rhs.is_raw)) {
|
||||
.resolved => |tid| {
|
||||
self.putTypeAlias(decl.source_file, cd.name, tid);
|
||||
@@ -1934,9 +1934,9 @@ pub fn structMethodFn(sd: *const ast.StructDecl, method: []const u8) ?*const ast
|
||||
|
||||
/// TRUE iff `ref` is a TYPE-FUNCTION head author — a `fn_decl` (or const-
|
||||
/// wrapped fn) declaring at least one `$`-parameter, i.e. instantiable as a
|
||||
/// bare type head (`Make(s64)` where `Make :: ($T) -> Type`). Mirrors the
|
||||
/// bare type head (`Make(i64)` where `Make :: ($T) -> Type`). Mirrors the
|
||||
/// `fd.type_params.len > 0` gate every instantiation site uses to recognize a
|
||||
/// type-fn head, so an ORDINARY same-name function (`Make :: () -> s32`, zero
|
||||
/// type-fn head, so an ORDINARY same-name function (`Make :: () -> i32`, zero
|
||||
/// type params) is NOT a type-fn author and does NOT vouch for a hidden 2-flat-
|
||||
/// hop type-fn head (E4 attempt-8: a `fn_decl != null` author view let any
|
||||
/// visible function — type-fn or not — authorize a type head).
|
||||
@@ -2262,7 +2262,7 @@ pub fn lazyLowerFunction(self: *Lowering, name: []const u8) void {
|
||||
// Defer functions with type-category matches until all types are registered.
|
||||
// any_to_string uses `if type == { case slice: ... }` which compiles a switch
|
||||
// with type tags from resolveTypeCategoryTags. This must happen AFTER main is
|
||||
// fully lowered so all types ([]s32, List__s32, etc.) are in the TypeTable.
|
||||
// fully lowered so all types ([]i32, List__i32, etc.) are in the TypeTable.
|
||||
if (!self.processing_deferred and std.mem.eql(u8, name, "any_to_string")) {
|
||||
self.deferred_type_fns.append(self.alloc, name) catch {};
|
||||
return;
|
||||
@@ -2557,8 +2557,8 @@ pub fn emitModuleConst(self: *Lowering, ci: ModuleConstInfo, author_source: ?[]c
|
||||
// accepted under the unified narrowing rule — materializes as its folded
|
||||
// int through the SAME `program_index.foldCountI64` the count / array-dim
|
||||
// path uses, so the const's emitted VALUE and its use as a COUNT come from
|
||||
// one fold (`K : s64 : 4.0` → 4; `K : s64 : M + 2.0` → 4; and a float-const-
|
||||
// leaf `KF : s64 : F + 1.5` → 4, which the int-only folder could not reach).
|
||||
// one fold (`K : i64 : 4.0` → 4; `K : i64 : M + 2.0` → 4; and a float-const-
|
||||
// leaf `KF : i64 : F + 1.5` → 4, which the int-only folder could not reach).
|
||||
// A non-integral float never arrives (it was rejected at registration); any
|
||||
// other non-foldable shape falls through to the per-kind emitters below.
|
||||
if (self.isIntEx(ci.ty)) {
|
||||
@@ -2597,5 +2597,5 @@ pub fn emitModuleConst(self: *Lowering, ci: ModuleConstInfo, author_source: ?[]c
|
||||
|
||||
pub fn emitPlaceholder(self: *Lowering, name: []const u8) Ref {
|
||||
const sid = self.module.types.internString(name);
|
||||
return self.builder.emit(.{ .placeholder = sid }, .s64);
|
||||
return self.builder.emit(.{ .placeholder = sid }, .i64);
|
||||
}
|
||||
|
||||
@@ -428,8 +428,8 @@ pub fn lowerCallerLocation(self: *Lowering, node: *const Node) Ref {
|
||||
const func_name = self.currentFunctionName();
|
||||
var fields = [_]Ref{
|
||||
self.builder.constString(self.module.types.internString(file)),
|
||||
self.builder.constInt(@intCast(loc.line), .s32),
|
||||
self.builder.constInt(@intCast(loc.col), .s32),
|
||||
self.builder.constInt(@intCast(loc.line), .i32),
|
||||
self.builder.constInt(@intCast(loc.col), .i32),
|
||||
self.builder.constString(self.module.types.internString(func_name)),
|
||||
};
|
||||
return self.builder.emit(.{ .struct_init = .{ .fields = self.alloc.dupe(Ref, &fields) catch unreachable } }, sl_tid);
|
||||
|
||||
@@ -82,7 +82,7 @@ pub fn lowerStructLiteral(self: *Lowering, sl: *const ast.StructLiteral, span: a
|
||||
// `.ambiguous`/`.not_visible` surface their loud diagnostic + poison.
|
||||
self.resolveNominalLeaf(name, false, span)
|
||||
else if (sl.type_expr) |te|
|
||||
// Generic struct literal: Pair(s32).{ ... } — resolve type from type_expr
|
||||
// Generic struct literal: Pair(i32).{ ... } — resolve type from type_expr
|
||||
self.resolveTypeWithBindings(te)
|
||||
else self.target_type orelse .unresolved;
|
||||
|
||||
@@ -315,7 +315,7 @@ pub fn fixupMethodReceiver(self: *Lowering, method_args: *std.ArrayList(Ref), fu
|
||||
/// Get the name of a struct type (dereferencing pointers). Returns null for non-struct types.
|
||||
pub fn getStructTypeName(self: *Lowering, ty: TypeId) ?[]const u8 {
|
||||
if (ty.isBuiltin()) {
|
||||
// Map builtin types to their names for method resolution (e.g., s64.eq)
|
||||
// Map builtin types to their names for method resolution (e.g., i64.eq)
|
||||
return builtinTypeName(ty);
|
||||
}
|
||||
var resolved = ty;
|
||||
@@ -333,10 +333,10 @@ pub fn getStructTypeName(self: *Lowering, ty: TypeId) ?[]const u8 {
|
||||
|
||||
pub fn builtinTypeName(ty: TypeId) ?[]const u8 {
|
||||
return switch (ty) {
|
||||
.s8 => "s8",
|
||||
.s16 => "s16",
|
||||
.s32 => "s32",
|
||||
.s64 => "s64",
|
||||
.i8 => "i8",
|
||||
.i16 => "i16",
|
||||
.i32 => "i32",
|
||||
.i64 => "i64",
|
||||
.u8 => "u8",
|
||||
.u16 => "u16",
|
||||
.u32 => "u32",
|
||||
@@ -351,7 +351,7 @@ pub fn builtinTypeName(ty: TypeId) ?[]const u8 {
|
||||
|
||||
/// Resolve the type of a named field on a given type.
|
||||
pub fn resolveFieldType(self: *Lowering, ty: TypeId, field: []const u8) TypeId {
|
||||
if (std.mem.eql(u8, field, "len")) return .s64;
|
||||
if (std.mem.eql(u8, field, "len")) return .i64;
|
||||
if (std.mem.eql(u8, field, "ptr")) {
|
||||
const elem_ty = self.getElementType(ty);
|
||||
return self.module.types.manyPtrTo(elem_ty);
|
||||
@@ -497,7 +497,7 @@ pub fn lowerFieldAccess(self: *Lowering, fa: *const ast.FieldAccess, span: ast.S
|
||||
if (self.pack_param_count) |ppc| {
|
||||
if (fa.object.data == .identifier and std.mem.eql(u8, fa.field, "len")) {
|
||||
if (ppc.get(fa.object.data.identifier.name)) |n| {
|
||||
return self.builder.constInt(@as(i64, @intCast(n)), .s64);
|
||||
return self.builder.constInt(@as(i64, @intCast(n)), .i64);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -606,7 +606,7 @@ pub fn lowerFieldAccess(self: *Lowering, fa: *const ast.FieldAccess, span: ast.S
|
||||
|
||||
if (is_special) {
|
||||
if (std.mem.eql(u8, fa.field, "len")) {
|
||||
return self.builder.emit(.{ .length = .{ .operand = obj } }, .s64);
|
||||
return self.builder.emit(.{ .length = .{ .operand = obj } }, .i64);
|
||||
}
|
||||
{
|
||||
const elem_ty = self.getElementType(obj_ty);
|
||||
@@ -658,7 +658,7 @@ pub fn identifierBindsValue(self: *Lowering, name: []const u8) bool {
|
||||
/// a builtin type (a user struct → ordinary field lowering reports
|
||||
/// field-not-found). Two clean diagnostics (then a placeholder, so lowering
|
||||
/// finishes and `hasErrors()` aborts the build):
|
||||
/// - a FLOAT-only accessor on an integer type (`s32.epsilon`, `u8.inf`);
|
||||
/// - a FLOAT-only accessor on an integer type (`i32.epsilon`, `u8.inf`);
|
||||
/// - any accessor on a builtin NON-numeric receiver
|
||||
/// (`bool`/`string`/`void`/`Any`/`noreturn`).
|
||||
pub fn lowerNumericLimit(self: *Lowering, fa: *const ast.FieldAccess, span: ast.Span) ?Ref {
|
||||
@@ -704,7 +704,7 @@ pub fn lowerStructConstant(self: *Lowering, info: StructConstInfo) Ref {
|
||||
return switch (val_node.data) {
|
||||
.int_literal => |lit| blk: {
|
||||
if (info.ty) |t| self.checkIntLiteralFits(lit.value, t, val_node.span);
|
||||
break :blk self.builder.constInt(lit.value, info.ty orelse .s64);
|
||||
break :blk self.builder.constInt(lit.value, info.ty orelse .i64);
|
||||
},
|
||||
.float_literal => |lit| self.builder.constFloat(lit.value, info.ty orelse .f64),
|
||||
.bool_literal => |lit| self.builder.constBool(lit.value),
|
||||
@@ -1199,9 +1199,9 @@ pub fn resolveArrayLiteralType(self: *Lowering, te: *const Node) TypeId {
|
||||
return self.module.types.vectorOf(elem, length);
|
||||
}
|
||||
}
|
||||
// Generic-struct typed-literal head (`Box(s64).[...]`): route
|
||||
// Generic-struct typed-literal head (`Box(i64).[...]`): route
|
||||
// through the single layout choke-point (CP-1). A qualified head
|
||||
// `a.Box(s64).[...]` selects a's OWN template via the namespace edge
|
||||
// `a.Box(i64).[...]` selects a's OWN template via the namespace edge
|
||||
// (Counter-1: was the global last-wins map); a bare head selects the
|
||||
// single bare-VISIBLE author.
|
||||
if (headNameOfCallee(cl.callee)) |hn| {
|
||||
@@ -1258,7 +1258,7 @@ pub fn lowerIndexExpr(self: *Lowering, ie: *const ast.IndexExpr) Ref {
|
||||
// bounds" instead of the generic "unresolved 'args'" that the
|
||||
// fall-through scope-lookup would produce.
|
||||
if (self.diagPackIndexOOB(ie)) {
|
||||
return self.builder.constInt(0, .s64);
|
||||
return self.builder.constInt(0, .i64);
|
||||
}
|
||||
// Runtime index into a comptime-only pack (Decision 1): a pack has no
|
||||
// runtime representation, so the index must be a compile-time constant.
|
||||
@@ -1271,7 +1271,7 @@ pub fn lowerIndexExpr(self: *Lowering, ie: *const ast.IndexExpr) Ref {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, ie.index.span, "pack '{s}' must be indexed by a compile-time constant — a pack is comptime-only and has no runtime value", .{pname});
|
||||
}
|
||||
return self.builder.constInt(0, .s64);
|
||||
return self.builder.constInt(0, .i64);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1306,10 +1306,10 @@ pub fn lowerIndexExpr(self: *Lowering, ie: *const ast.IndexExpr) Ref {
|
||||
|
||||
pub fn lowerSliceExpr(self: *Lowering, se: *const ast.SliceExpr) Ref {
|
||||
const obj = self.lowerExpr(se.object);
|
||||
var lo = if (se.start) |s| self.lowerExpr(s) else self.builder.constInt(0, .s64);
|
||||
if (se.start_exclusive) lo = self.builder.add(lo, self.builder.constInt(1, .s64), .s64);
|
||||
var hi = if (se.end) |e| self.lowerExpr(e) else self.builder.emit(.{ .length = .{ .operand = obj } }, .s64);
|
||||
if (se.end_inclusive) hi = self.builder.add(hi, self.builder.constInt(1, .s64), .s64);
|
||||
var lo = if (se.start) |s| self.lowerExpr(s) else self.builder.constInt(0, .i64);
|
||||
if (se.start_exclusive) lo = self.builder.add(lo, self.builder.constInt(1, .i64), .i64);
|
||||
var hi = if (se.end) |e| self.lowerExpr(e) else self.builder.emit(.{ .length = .{ .operand = obj } }, .i64);
|
||||
if (se.end_inclusive) hi = self.builder.add(hi, self.builder.constInt(1, .i64), .i64);
|
||||
// Infer result slice type from the object
|
||||
const obj_ty = self.inferExprType(se.object);
|
||||
// Subslice of string stays string (same {ptr, i64} layout, correct type category)
|
||||
@@ -1598,8 +1598,8 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
}
|
||||
}
|
||||
const ty = if (self.target_type) |tt| blk: {
|
||||
break :blk if (self.isIntEx(tt)) tt else .s64;
|
||||
} else .s64;
|
||||
break :blk if (self.isIntEx(tt)) tt else .i64;
|
||||
} else .i64;
|
||||
self.checkIntLiteralFits(lit.value, ty, node.span);
|
||||
return self.builder.constInt(lit.value, ty);
|
||||
},
|
||||
@@ -1645,7 +1645,7 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
// Check compile-time constants (OS, ARCH, POINTER_SIZE) before globals
|
||||
if (self.comptime_constants.get(id.name)) |cv| {
|
||||
switch (cv) {
|
||||
.int_val => |iv| break :blk self.builder.constInt(iv, .s64),
|
||||
.int_val => |iv| break :blk self.builder.constInt(iv, .i64),
|
||||
.enum_tag => |et| break :blk self.builder.constInt(@intCast(et.tag), et.ty),
|
||||
}
|
||||
}
|
||||
@@ -1683,7 +1683,7 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
.untracked => break :blk self.builder.emit(.{ .global_get = gi.id }, gi.ty),
|
||||
}
|
||||
}
|
||||
// Check module-level value constants (e.g. AF_INET :s32: 2)
|
||||
// Check module-level value constants (e.g. AF_INET :i32: 2)
|
||||
if (self.program_index.module_const_map.get(id.name)) |ci_global| {
|
||||
if (!self.isNameVisible(id.name)) {
|
||||
if (self.diagnostics) |d|
|
||||
@@ -1820,7 +1820,7 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
// examples/50-smoke.sx has both shapes.
|
||||
}
|
||||
}
|
||||
break :blk self.builder.emit(.{ .func_ref = fid }, .s64);
|
||||
break :blk self.builder.emit(.{ .func_ref = fid }, .i64);
|
||||
}
|
||||
}
|
||||
// Type-as-value: a name that resolves to a TypeId
|
||||
@@ -1927,7 +1927,7 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
break :blk self.builder.constFloat(@floatFromInt(v), tt);
|
||||
}
|
||||
}
|
||||
const nty = if (self.target_type) |tt| (if (self.isIntEx(tt)) tt else TypeId.s64) else TypeId.s64;
|
||||
const nty = if (self.target_type) |tt| (if (self.isIntEx(tt)) tt else TypeId.i64) else TypeId.i64;
|
||||
self.checkIntLiteralFits(v, nty, node.span);
|
||||
break :blk self.builder.constInt(v, nty);
|
||||
}
|
||||
@@ -2031,7 +2031,7 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
},
|
||||
|
||||
// type_expr can appear as a variable reference when the name collides
|
||||
// with a builtin type name (e.g. s2, u8). Check scope first.
|
||||
// with a builtin type name (e.g. i2, u8). Check scope first.
|
||||
.type_expr => |te| blk: {
|
||||
if (self.scope) |scope| {
|
||||
if (scope.lookup(te.name)) |binding| {
|
||||
@@ -2057,8 +2057,8 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
|
||||
// Compound type literals (`*T`, `[]T`, `[*]T`, `?T`, `[N]T`, fn types)
|
||||
// in expression position are first-class `Type` values, exactly like
|
||||
// the named form above (`t : Type = *s64;` ↔ `t : Type = f64;`). Also
|
||||
// the path a static `cast(*s64) v` type argument takes — call args are
|
||||
// the named form above (`t : Type = *i64;` ↔ `t : Type = f64;`). Also
|
||||
// the path a static `cast(*i64) v` type argument takes — call args are
|
||||
// lowered before the cast handler inspects the AST (issue 0118).
|
||||
.pointer_type_expr,
|
||||
.many_pointer_type_expr,
|
||||
@@ -2128,7 +2128,7 @@ pub fn lowerBinaryOp(self: *Lowering, bop: *const ast.BinaryOp) Ref {
|
||||
}
|
||||
|
||||
// Type-literal comparison fold: when both sides are type-shaped
|
||||
// AST nodes (`s64`, `*u8`, `?T`, `[3]f64`, etc.) OR resolve to
|
||||
// AST nodes (`i64`, `*u8`, `?T`, `[3]f64`, etc.) OR resolve to
|
||||
// a static TypeId at lower time (`type_of(x)` for any
|
||||
// statically-typed `x`), resolve each and emit a `const_bool`.
|
||||
// Same semantic as `type_eq(A, B)` but using the standard `==`
|
||||
@@ -2144,11 +2144,11 @@ pub fn lowerBinaryOp(self: *Lowering, bop: *const ast.BinaryOp) Ref {
|
||||
}
|
||||
}
|
||||
|
||||
// Any-shaped `==` (e.g. `t == s64` where `t: Type`): both
|
||||
// Any-shaped `==` (e.g. `t == i64` where `t: Type`): both
|
||||
// operands are 16-byte `{tag, value}` aggregates. LLVM
|
||||
// doesn't accept `icmp` on aggregates directly. Decompose
|
||||
// via `unbox_any` (which extracts the value field at
|
||||
// `.s64`) and compare the i64s. Tag fields are stable
|
||||
// `.i64`) and compare the i64s. Tag fields are stable
|
||||
// across compilations of the same source so value-only
|
||||
// identity is enough.
|
||||
if (bop.op == .eq or bop.op == .neq) {
|
||||
@@ -2157,8 +2157,8 @@ pub fn lowerBinaryOp(self: *Lowering, bop: *const ast.BinaryOp) Ref {
|
||||
if (lhs_ty == .any and rhs_ty == .any) {
|
||||
const lhs = self.lowerExpr(bop.lhs);
|
||||
const rhs = self.lowerExpr(bop.rhs);
|
||||
const lhs_val = self.builder.emit(.{ .unbox_any = .{ .operand = lhs } }, .s64);
|
||||
const rhs_val = self.builder.emit(.{ .unbox_any = .{ .operand = rhs } }, .s64);
|
||||
const lhs_val = self.builder.emit(.{ .unbox_any = .{ .operand = lhs } }, .i64);
|
||||
const rhs_val = self.builder.emit(.{ .unbox_any = .{ .operand = rhs } }, .i64);
|
||||
if (bop.op == .eq) {
|
||||
return self.builder.emit(.{ .cmp_eq = .{ .lhs = lhs_val, .rhs = rhs_val } }, .bool);
|
||||
} else {
|
||||
@@ -2249,7 +2249,7 @@ pub fn lowerBinaryOp(self: *Lowering, bop: *const ast.BinaryOp) Ref {
|
||||
if (rhs_ref_pointee) |p| rhs = self.builder.load(rhs, p);
|
||||
self.target_type = saved_tt;
|
||||
// Result type follows the shared promotion rule: an int LHS with a
|
||||
// float RHS promotes to the float (`s64 * f32` → `f32`); vectors /
|
||||
// float RHS promotes to the float (`i64 * f32` → `f32`); vectors /
|
||||
// structs keep the LHS type. `inferExprType` reuses the same helper
|
||||
// so static typing agrees with the value produced here.
|
||||
const rhs_inferred = rhs_ref_pointee orelse self.inferExprType(bop.rhs);
|
||||
@@ -2298,7 +2298,7 @@ pub fn lowerBinaryOp(self: *Lowering, bop: *const ast.BinaryOp) Ref {
|
||||
}
|
||||
|
||||
// Reject scalar ops on incompatible operand types (e.g.
|
||||
// `s64 + string`, `s64 < string`, `s64 & string`). The result type
|
||||
// `i64 + string`, `i64 < string`, `i64 & string`). The result type
|
||||
// `ty` is derived from the LHS, so without this the op lowers as
|
||||
// `<op> : <lhs>` and either reinterprets the RHS bytes (arithmetic
|
||||
// / bitwise → garbage) or feeds mismatched LLVM types to `icmp`
|
||||
@@ -2448,7 +2448,7 @@ pub fn lowerTupleOp(self: *Lowering, bop: *const ast.BinaryOp, lhs: Ref, rhs: Re
|
||||
// Lexicographic comparison
|
||||
return self.lowerTupleLexCompare(bop.op, lhs, rhs, lhs_fields);
|
||||
},
|
||||
else => return self.builder.constInt(0, .s64),
|
||||
else => return self.builder.constInt(0, .i64),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -318,14 +318,14 @@ pub fn lowerForeignMethodCall(
|
||||
const ret_ty = if (method.return_type) |rt| self.resolveType(rt) else .void;
|
||||
|
||||
// Reject return types the JNI emit path can't dispatch — emit_llvm's
|
||||
// Call<T>Method switch only covers void / bool / s32 / s64 / f32 / f64
|
||||
// / pointer-returning. Anything else (s8 / s16 / u8 / u16 / aggregates)
|
||||
// Call<T>Method switch only covers void / bool / i32 / i64 / f32 / f64
|
||||
// / pointer-returning. Anything else (i8 / i16 / u8 / u16 / aggregates)
|
||||
// would silently lower to LLVMGetUndef and produce wrong arguments at
|
||||
// the call site (chess Android touch shipped broken because s32→s32+
|
||||
// the call site (chess Android touch shipped broken because i32→i32+
|
||||
// f32 returns hit the undef path before .f32 was wired up).
|
||||
if (!jni_descriptor.isJniReturnTypeSupported(&self.module.types, ret_ty)) {
|
||||
if (self.diagnostics) |d| {
|
||||
d.addFmt(.err, span, "JNI method '{s}.{s}' returns '{s}', which isn't supported by the JNI call-method lowering yet — only void/bool/s32/s64/f32/f64 and pointers are wired up", .{ fcd.name, method.name, self.module.types.typeName(ret_ty) });
|
||||
d.addFmt(.err, span, "JNI method '{s}.{s}' returns '{s}', which isn't supported by the JNI call-method lowering yet — only void/bool/i32/i64/f32/f64 and pointers are wired up", .{ fcd.name, method.name, self.module.types.typeName(ret_ty) });
|
||||
}
|
||||
return Ref.none;
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ pub fn monomorphizeFunction(self: *Lowering, fd: *const ast.FnDecl, mangled_name
|
||||
/// - type_expr AST nodes
|
||||
/// True iff `node` matches an AST shape that `resolveTypeArg`
|
||||
/// can resolve to a concrete TypeId without falling through to
|
||||
/// the silent `.s64` default. Used by `tryLowerReflectionCall`
|
||||
/// the silent `.i64` default. Used by `tryLowerReflectionCall`
|
||||
/// to split static-fold from dynamic-builtin-call paths.
|
||||
///
|
||||
/// Static-arg shapes mirror the explicit arms of `resolveTypeArg`:
|
||||
@@ -206,9 +206,9 @@ pub fn monomorphizeFunction(self: *Lowering, fd: *const ast.FnDecl, mangled_name
|
||||
pub fn isStaticTypeArg(self: *Lowering, node: *const Node) bool {
|
||||
switch (node.data) {
|
||||
.type_expr => |te| {
|
||||
// A type-keyword name (e.g. `s64`) is always static.
|
||||
// A type-keyword name (e.g. `i64`) is always static.
|
||||
// A user-defined name that happens to be in scope as
|
||||
// a runtime variable (`x: Type = s64; type_name(x)`)
|
||||
// a runtime variable (`x: Type = i64; type_name(x)`)
|
||||
// is NOT static — route through the dynamic builtin
|
||||
// call so the runtime lookup table fires.
|
||||
if (self.scope) |scope| {
|
||||
@@ -245,7 +245,7 @@ pub fn isStaticTypeArg(self: *Lowering, node: *const Node) bool {
|
||||
pub fn isStaticTypeRef(self: *Lowering, node: *const Node) bool {
|
||||
switch (node.data) {
|
||||
.type_expr => |te| {
|
||||
// Compound type names (`s64`, `Point`, `Vec4`) resolve
|
||||
// Compound type names (`i64`, `Point`, `Vec4`) resolve
|
||||
// statically. If the name is also a runtime var in
|
||||
// scope, it's a value reference, not a type ref.
|
||||
if (self.scope) |scope| {
|
||||
@@ -286,10 +286,10 @@ pub fn isStaticTypeRef(self: *Lowering, node: *const Node) bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a tuple LITERAL used in a type position (`(s32, s32)` reinterpreted
|
||||
/// Resolve a tuple LITERAL used in a type position (`(i32, i32)` reinterpreted
|
||||
/// as a tuple type at a type-demanding site such as `size_of`). Every element
|
||||
/// must itself denote a type; a non-type element — e.g. the `1` in
|
||||
/// `(s32, 1)` — is a user error. Emit a diagnostic pointing at the offending
|
||||
/// `(i32, 1)` — is a user error. Emit a diagnostic pointing at the offending
|
||||
/// element and return `.unresolved`; never fabricate a tuple with a bogus
|
||||
/// field. type_bridge.resolveAstType builds the tuple only after
|
||||
/// this validation passes.
|
||||
@@ -297,12 +297,12 @@ pub fn resolveTupleLiteralTypeArg(self: *Lowering, node: *const Node) TypeId {
|
||||
for (node.data.tuple_literal.elements) |el| {
|
||||
if (!type_bridge.isTypeShapedAstNode(el.value, &self.module.types)) {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, el.value.span, "tuple type element is not a type (found `{s}`); a tuple used as a type must list only types, e.g. `(s32, s32)`", .{@tagName(el.value.data)});
|
||||
diags.addFmt(.err, el.value.span, "tuple type element is not a type (found `{s}`); a tuple used as a type must list only types, e.g. `(i32, i32)`", .{@tagName(el.value.data)});
|
||||
}
|
||||
return .unresolved;
|
||||
}
|
||||
// E4 single-hop visibility gate: each element leaf is resolved through
|
||||
// the source-aware resolver, so a 2-flat-hop inner leaf (`(COnly, s64)`)
|
||||
// the source-aware resolver, so a 2-flat-hop inner leaf (`(COnly, i64)`)
|
||||
// emits "not visible" + poisons rather than leaking through
|
||||
// `type_bridge`'s ungated global lookup. A valid element resolves to the
|
||||
// same TypeId the delegated build produces below (no diagnostic, no
|
||||
@@ -314,10 +314,10 @@ pub fn resolveTupleLiteralTypeArg(self: *Lowering, node: *const Node) TypeId {
|
||||
|
||||
pub fn resolveTypeArg(self: *Lowering, node: *const Node) TypeId {
|
||||
// Pack-index access in a type-arg slot (e.g. `type_name($args[0])`
|
||||
// or `type_eq($args[i], s64)`). Same shape as the
|
||||
// or `type_eq($args[i], i64)`). Same shape as the
|
||||
// `resolveTypeWithBindings` arm — looks up the bound pack types
|
||||
// and returns the i-th. OOB and no-active-binding emit focused
|
||||
// diagnostics rather than silently defaulting to .s64 (the
|
||||
// diagnostics rather than silently defaulting to .i64 (the
|
||||
// catch-all `else` below) — that fall-through is exactly the
|
||||
// "silent unimplemented arm" the project's REJECTED PATTERNS
|
||||
// forbid.
|
||||
@@ -392,8 +392,8 @@ pub fn resolveTypeArg(self: *Lowering, node: *const Node) TypeId {
|
||||
// time when `x`'s type is statically known (which it
|
||||
// is for any expression — type inference always
|
||||
// produces a concrete TypeId). Lets
|
||||
// `type_of(a) == s64` fold the same as
|
||||
// `inferExprType(a) == s64`.
|
||||
// `type_of(a) == i64` fold the same as
|
||||
// `inferExprType(a) == i64`.
|
||||
if (cl.callee.data == .identifier and
|
||||
std.mem.eql(u8, cl.callee.data.identifier.name, "type_of") and
|
||||
cl.args.len == 1)
|
||||
@@ -407,7 +407,7 @@ pub fn resolveTypeArg(self: *Lowering, node: *const Node) TypeId {
|
||||
// route through the gated `resolveTypeWithBindings`, whose
|
||||
// `resolveCompound` recurses each element through the source-aware leaf
|
||||
// (`resolveNominalLeaf`) — so a 2-hop inner leaf (`*COnly`, `[2]COnly`,
|
||||
// `(COnly, s64)`) is rejected exactly as in a normal annotation, instead
|
||||
// `(COnly, i64)`) is rejected exactly as in a normal annotation, instead
|
||||
// of `type_bridge.resolveAstType`'s ungated global lookup (E4).
|
||||
.tuple_literal,
|
||||
.pointer_type_expr,
|
||||
@@ -421,13 +421,13 @@ pub fn resolveTypeArg(self: *Lowering, node: *const Node) TypeId {
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a type name for display (e.g. "*Point", "[]s32", "[3]f64").
|
||||
/// Format a type name for display (e.g. "*Point", "[]i32", "[3]f64").
|
||||
pub fn formatTypeName(self: *Lowering, ty: TypeId) []const u8 {
|
||||
// Builtin types: use their canonical name
|
||||
if (ty == .s8) return "s8";
|
||||
if (ty == .s16) return "s16";
|
||||
if (ty == .s32) return "s32";
|
||||
if (ty == .s64) return "s64";
|
||||
if (ty == .i8) return "i8";
|
||||
if (ty == .i16) return "i16";
|
||||
if (ty == .i32) return "i32";
|
||||
if (ty == .i64) return "i64";
|
||||
if (ty == .u8) return "u8";
|
||||
if (ty == .u16) return "u16";
|
||||
if (ty == .u32) return "u32";
|
||||
@@ -463,7 +463,7 @@ pub fn formatTypeName(self: *Lowering, ty: TypeId) []const u8 {
|
||||
const inner = self.formatTypeName(a.element);
|
||||
break :blk std.fmt.allocPrint(self.alloc, "[{d}]{s}", .{ a.length, inner }) catch "array";
|
||||
},
|
||||
.signed => |w| std.fmt.allocPrint(self.alloc, "s{d}", .{w}) catch "signed",
|
||||
.signed => |w| std.fmt.allocPrint(self.alloc, "i{d}", .{w}) catch "signed",
|
||||
.unsigned => |w| std.fmt.allocPrint(self.alloc, "u{d}", .{w}) catch "unsigned",
|
||||
.optional => |o| blk: {
|
||||
const inner = self.formatTypeName(o.child);
|
||||
@@ -477,7 +477,7 @@ pub fn formatTypeName(self: *Lowering, ty: TypeId) []const u8 {
|
||||
};
|
||||
}
|
||||
|
||||
/// Format a function type string like "() -> s32" or "(s32, s32) -> s32".
|
||||
/// Format a function type string like "() -> i32" or "(i32, i32) -> i32".
|
||||
pub fn formatFnTypeString(self: *Lowering, fd: *const ast.FnDecl) []const u8 {
|
||||
var buf: [512]u8 = undefined;
|
||||
var pos: usize = 0;
|
||||
@@ -509,7 +509,7 @@ pub fn formatFnTypeString(self: *Lowering, fd: *const ast.FnDecl) []const u8 {
|
||||
}
|
||||
|
||||
/// Format a type name for function name mangling (identifier-safe).
|
||||
/// E.g. *Point → "ptr_Point", []s32 → "slice_s32", [3]f64 → "array_3_f64".
|
||||
/// E.g. *Point → "ptr_Point", []i32 → "slice_i32", [3]f64 → "array_3_f64".
|
||||
/// Check if a param type expression references a type param name (possibly nested).
|
||||
pub fn matchTypeParam(_: *Lowering, type_node: *const Node, tp_name: []const u8) bool {
|
||||
return switch (type_node.data) {
|
||||
@@ -548,7 +548,7 @@ pub fn matchTypeParamStatic(type_node: *const Node, tp_name: []const u8) bool {
|
||||
}
|
||||
|
||||
/// Extract the concrete type that corresponds to a type param from an arg type.
|
||||
/// E.g., param type []$T with arg type []s64 → T = s64.
|
||||
/// E.g., param type []$T with arg type []i64 → T = i64.
|
||||
pub fn extractTypeParam(self: *Lowering, type_node: *const Node, arg_ty: TypeId, tp_name: []const u8) ?TypeId {
|
||||
return switch (type_node.data) {
|
||||
.type_expr => |te| if (std.mem.eql(u8, te.name, tp_name)) arg_ty else null,
|
||||
@@ -635,10 +635,10 @@ pub fn resolveTypeCategoryTags(self: *Lowering, name: []const u8) []const u64 {
|
||||
|
||||
// Fixed builtin categories
|
||||
if (std.mem.eql(u8, name, "int")) {
|
||||
tags.append(self.alloc, TypeId.s8.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.s16.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.s32.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.s64.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.i8.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.i16.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.i32.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.i64.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.u8.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.u16.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.u32.index()) catch {};
|
||||
@@ -823,7 +823,7 @@ pub fn isPlainFreeFn(fd: *const ast.FnDecl) bool {
|
||||
/// Resolve a generic value-param argument (`$K: u32`) to its compile-time
|
||||
/// integer AND verify it fits the param's declared integer type. The folded
|
||||
/// value is bound and mangled into the instantiation name, so a module/generic
|
||||
/// const arg (`Vec(N, f32)`), a const expression (`Make(M + 1, s64)`), an
|
||||
/// const arg (`Vec(N, f32)`), a const expression (`Make(M + 1, i64)`), an
|
||||
/// integral float (`Box(4.0)` → 4), and a literal (`Vec(3, f32)`) all bind the
|
||||
/// same value a literal would. An out-of-range arg (`Box(5_000_000_000)` for a
|
||||
/// `u32` param) or a non-const arg emits a clean diagnostic and returns null;
|
||||
@@ -838,8 +838,8 @@ pub fn isPlainFreeFn(fd: *const ast.FnDecl) bool {
|
||||
/// `program_index.intTypeRange`; an unrecognised type folds without bounding.
|
||||
pub fn resolveValueParamArg(self: *Lowering, arg_node: *const Node, param_name: []const u8, type_name: ?[]const u8) ?i64 {
|
||||
// Resolve an ALIASED integer constraint (`$K: Count` where `Count :: u32`,
|
||||
// `$K: Small` where `Small :: s8`) to its underlying builtin so the range
|
||||
// gate below treats it exactly like `$K: u32` / `$K: s8` (an
|
||||
// `$K: Small` where `Small :: i8`) to its underlying builtin so the range
|
||||
// gate below treats it exactly like `$K: u32` / `$K: i8` (an
|
||||
// alias previously slipped past `intTypeRange`, so `Box(5_000_000_000)`
|
||||
// with `$K: Count` bound a truncated value). A non-integer / unrecognised
|
||||
// constraint yields null → no range bound (fold only), as before.
|
||||
@@ -887,7 +887,7 @@ pub fn resolveValueParamArg(self: *Lowering, arg_node: *const Node, param_name:
|
||||
|
||||
/// Resolve a generic value-param constraint type NAME to its canonical builtin
|
||||
/// integer type name, chasing a type alias (`Count :: u32` → "u32",
|
||||
/// `Small :: s8` → "s8") so an ALIASED integer constraint range-checks exactly
|
||||
/// `Small :: i8` → "i8") so an ALIASED integer constraint range-checks exactly
|
||||
/// like the builtin it names. Returns the name unchanged when it is already a
|
||||
/// builtin integer; null when it isn't an integer type (directly or via alias)
|
||||
/// — the caller then folds without a range bound rather than guessing. The
|
||||
@@ -914,8 +914,8 @@ pub fn diagValueParamRange(self: *Lowering, arg_node: *const Node, param_name: [
|
||||
|
||||
/// The poison-vs-proceed projection of `headTypeGate` for an UNQUALIFIED
|
||||
/// parameterized type HEAD that names a generic STRUCT, a parameterized
|
||||
/// PROTOCOL, or a type-returning function used as a head (`Box(s64)`,
|
||||
/// `VL(s64)`) — and the alias-registration / type-match sites that likewise
|
||||
/// PROTOCOL, or a type-returning function used as a head (`Box(i64)`,
|
||||
/// `VL(i64)`) — and the alias-registration / type-match sites that likewise
|
||||
/// only need "poison or proceed". Returns TRUE (the gate's loud diagnostic is
|
||||
/// already emitted) when the head is `.not_visible` (a 2-flat-hop leak) or
|
||||
/// `.ambiguous` (≥2 direct flat same-name authors — consistent with the leaf /
|
||||
@@ -1186,7 +1186,7 @@ pub fn flatFnAuthorAmbiguous(self: *Lowering, name: []const u8, from: []const u8
|
||||
/// analogue of `isNameVisible` for a type-fn head: a same-name 1-hop
|
||||
/// NON-function (a value const `Make :: 123`, a named type) does NOT vouch
|
||||
/// (attempt-7), and — crucially — neither does a same-name 1-hop ORDINARY
|
||||
/// function (`Make :: () -> s32`, zero `$`-params), which cannot be the type
|
||||
/// function (`Make :: () -> i32`, zero `$`-params), which cannot be the type
|
||||
/// head being instantiated (attempt-8). So a type-fn whose only directly-
|
||||
/// visible same-name author is a non-fn OR a non-type-fn — its real author 2
|
||||
/// flat hops away — is correctly invisible. Mirrors `flatFnAuthorAmbiguous`'s
|
||||
@@ -1290,7 +1290,7 @@ pub fn resolveParameterizedWithBindings(self: *Lowering, pt: *const ast.Paramete
|
||||
}
|
||||
}
|
||||
|
||||
// Parameterized protocol used as a value type (`VL(s64)`): materialize a
|
||||
// Parameterized protocol used as a value type (`VL(i64)`): materialize a
|
||||
// 16-byte protocol value with the type-arg bound (not a 0-field stub).
|
||||
if (self.program_index.protocol_ast_map.get(base_name)) |pd| {
|
||||
if (pd.type_params.len > 0) {
|
||||
@@ -1300,7 +1300,7 @@ pub fn resolveParameterizedWithBindings(self: *Lowering, pt: *const ast.Paramete
|
||||
}
|
||||
|
||||
// User-defined type-returning function used as a TYPE annotation
|
||||
// (`b : Make(N, s64)` where `Make :: ($K: u32, $T: Type) -> Type`). The
|
||||
// (`b : Make(N, i64)` where `Make :: ($K: u32, $T: Type) -> Type`). The
|
||||
// `.call`-node path (`resolveTypeCallWithBindings`) already routes here;
|
||||
// a `parameterized_type_expr` must too, or the function name falls through
|
||||
// to the empty-struct stub below and `b.field` / `b.len` fails.
|
||||
@@ -1409,7 +1409,7 @@ pub fn instantiateGenericStruct(self: *Lowering, tmpl: *const StructTemplate, ar
|
||||
// A qualified `ns.Box(..)` head can select a generic template whose bare
|
||||
// name also belongs to a DIFFERENT module's same-name template (the one
|
||||
// that won the last-wins `struct_template_map`). Both would mangle to
|
||||
// `Box__s64` and the second instantiation would alias the first's layout.
|
||||
// `Box__i64` and the second instantiation would alias the first's layout.
|
||||
// Tag the NON-canonical author's mangled name with its source so each
|
||||
// author's instantiation is a distinct type. The canonical (bare-map)
|
||||
// author keeps the untagged name — no churn for single-author generics.
|
||||
@@ -1548,7 +1548,7 @@ pub fn instantiateGenericStruct(self: *Lowering, tmpl: *const StructTemplate, ar
|
||||
table.updatePreservingKey(id, info);
|
||||
|
||||
// Bind the template name to this concrete instance so a method's
|
||||
// `self: *Combined` (the template name) resolves to `*Combined__s64_s64`
|
||||
// `self: *Combined` (the template name) resolves to `*Combined__i64_i64`
|
||||
// — otherwise `self.field` hits the 0-field generic stub.
|
||||
tb.put(tmpl.name, id) catch {};
|
||||
|
||||
@@ -1694,7 +1694,7 @@ pub fn instantiateTypeFunction(self: *Lowering, alias_name: []const u8, template
|
||||
// struct/union/enum — `return [K]T`, `Vector(K, T)`, `*T`, an alias, etc.
|
||||
// Resolve it with the value/type bindings active (so `[K]T` folds K to a
|
||||
// compile-time integer). The result is interned structurally, so
|
||||
// `Make(N, s64)`, `Make(3, s64)`, and `Make(M + 1, s64)` all yield the
|
||||
// `Make(N, i64)`, `Make(3, i64)`, and `Make(M + 1, i64)` all yield the
|
||||
// same TypeId. `.unresolved` means the return wasn't a type expression
|
||||
// (e.g. a value-returning function in a type position) → fall through to
|
||||
// the caller's fallback rather than fabricating a type.
|
||||
@@ -1741,7 +1741,7 @@ pub fn instantiateTypeUnion(self: *Lowering, alias_name: []const u8, mangled_nam
|
||||
const info: types.TypeInfo = .{ .tagged_union = .{
|
||||
.name = alias_name_id,
|
||||
.fields = variant_fields.items,
|
||||
.tag_type = .s64,
|
||||
.tag_type = .i64,
|
||||
} };
|
||||
const id = if (table.findByName(alias_name_id)) |existing| existing else table.intern(info);
|
||||
table.updatePreservingKey(id, info);
|
||||
@@ -1752,7 +1752,7 @@ pub fn instantiateTypeUnion(self: *Lowering, alias_name: []const u8, mangled_nam
|
||||
const mangled_info: types.TypeInfo = .{ .tagged_union = .{
|
||||
.name = mangled_name_id,
|
||||
.fields = variant_fields.items,
|
||||
.tag_type = .s64,
|
||||
.tag_type = .i64,
|
||||
} };
|
||||
const mid = if (table.findByName(mangled_name_id)) |existing| existing else table.intern(mangled_info);
|
||||
table.updatePreservingKey(mid, mangled_info);
|
||||
|
||||
@@ -102,7 +102,7 @@ pub fn reserveShadowEnumSlot(self: *Lowering, ed: *const ast.EnumDecl) void {
|
||||
const name_id = table.internString(ed.name);
|
||||
const nominal_id = self.shadowNominalId(name_id);
|
||||
const empty: types.TypeInfo = if (ed.variant_types.len > 0)
|
||||
.{ .tagged_union = .{ .name = name_id, .fields = &.{}, .tag_type = .s64 } }
|
||||
.{ .tagged_union = .{ .name = name_id, .fields = &.{}, .tag_type = .i64 } }
|
||||
else
|
||||
.{ .@"enum" = .{ .name = name_id, .variants = &.{} } };
|
||||
const reserved = table.internNominal(empty, nominal_id);
|
||||
@@ -534,7 +534,7 @@ pub fn bareVisibleStructTemplate(self: *Lowering, name: []const u8) ?StructTempl
|
||||
}
|
||||
|
||||
/// Instantiate a generic struct template and register the result under an
|
||||
/// alias name (`Vec3 :: Vec(3, f32)` / `ABox :: a.Box(s64)`). Shared by the
|
||||
/// alias name (`Vec3 :: Vec(3, f32)` / `ABox :: a.Box(i64)`). Shared by the
|
||||
/// `.call` and `.parameterized_type_expr` const-decl alias branches and the
|
||||
/// qualified-head selection that precedes the bare `struct_template_map`
|
||||
/// fallback in each.
|
||||
|
||||
@@ -906,10 +906,10 @@ pub fn emitObjcDefinedAllocAndInit(
|
||||
|
||||
// (3) memset(state, 0, STATE_SIZE) — zero everything including the
|
||||
// allocator slot; the next store re-writes the allocator slot.
|
||||
const memset_fid = self.ensureCRuntimeDecl("memset", &.{ ptr_void, .s32, .u64 }, ptr_void);
|
||||
const memset_fid = self.ensureCRuntimeDecl("memset", &.{ ptr_void, .i32, .u64 }, ptr_void);
|
||||
const memset_args = self.alloc.alloc(Ref, 3) catch return null;
|
||||
memset_args[0] = state;
|
||||
memset_args[1] = self.builder.constInt(0, .s32);
|
||||
memset_args[1] = self.builder.constInt(0, .i32);
|
||||
memset_args[2] = size_const;
|
||||
_ = self.builder.emit(.{ .call = .{ .callee = memset_fid, .args = memset_args } }, ptr_void);
|
||||
|
||||
|
||||
@@ -176,10 +176,10 @@ pub fn lowerPackToSlice(self: *Lowering, pack_name: []const u8, slice_ty: TypeId
|
||||
};
|
||||
const slice_slot = self.builder.alloca(slice_ty);
|
||||
const ptr_gep = self.builder.structGepTyped(slice_slot, 0, self.module.types.ptrTo(elem_ty), slice_ty);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, slice_ty);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, slice_ty);
|
||||
if (arg_nodes.len == 0) {
|
||||
self.builder.store(ptr_gep, self.builder.constNull(self.module.types.ptrTo(elem_ty)));
|
||||
self.builder.store(len_gep, self.builder.constInt(0, .s64));
|
||||
self.builder.store(len_gep, self.builder.constInt(0, .i64));
|
||||
return self.builder.load(slice_slot, slice_ty);
|
||||
}
|
||||
const array_ty = self.module.types.arrayOf(elem_ty, @intCast(arg_nodes.len));
|
||||
@@ -193,12 +193,12 @@ pub fn lowerPackToSlice(self: *Lowering, pack_name: []const u8, slice_ty: TypeId
|
||||
} else if (elem_is_protocol) {
|
||||
if (source_ty != elem_ty) val = self.buildProtocolErasure(val, arg, source_ty, elem_ty);
|
||||
}
|
||||
const ep = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = self.builder.constInt(@intCast(i), .s64) } }, self.module.types.ptrTo(elem_ty));
|
||||
const ep = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = self.builder.constInt(@intCast(i), .i64) } }, self.module.types.ptrTo(elem_ty));
|
||||
self.builder.store(ep, val);
|
||||
}
|
||||
const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = self.builder.constInt(0, .s64) } }, self.module.types.ptrTo(elem_ty));
|
||||
const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = self.builder.constInt(0, .i64) } }, self.module.types.ptrTo(elem_ty));
|
||||
self.builder.store(ptr_gep, data_ptr);
|
||||
self.builder.store(len_gep, self.builder.constInt(@intCast(arg_nodes.len), .s64));
|
||||
self.builder.store(len_gep, self.builder.constInt(@intCast(arg_nodes.len), .i64));
|
||||
return self.builder.load(slice_slot, slice_ty);
|
||||
}
|
||||
|
||||
@@ -211,12 +211,12 @@ pub fn lowerVariadicArgs(self: *Lowering, param_name: []const u8, call_args: []c
|
||||
if (n == 0) {
|
||||
// Empty slice: {null, 0}
|
||||
const null_ptr = self.builder.constNull(self.module.types.ptrTo(.any));
|
||||
const zero_len = self.builder.constInt(0, .s64);
|
||||
const zero_len = self.builder.constInt(0, .i64);
|
||||
const slice_slot = self.builder.alloca(any_slice_ty);
|
||||
// Store ptr (field 0) and len (field 1) into the slice alloca
|
||||
const ptr_gep = self.builder.structGepTyped(slice_slot, 0, self.module.types.ptrTo(.any), any_slice_ty);
|
||||
self.builder.store(ptr_gep, null_ptr);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, any_slice_ty);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, any_slice_ty);
|
||||
self.builder.store(len_gep, zero_len);
|
||||
if (self.scope) |scope| {
|
||||
scope.put(param_name, .{ .ref = slice_slot, .ty = any_slice_ty, .is_alloca = true });
|
||||
@@ -232,7 +232,7 @@ pub fn lowerVariadicArgs(self: *Lowering, param_name: []const u8, call_args: []c
|
||||
for (call_args[start_idx..], 0..) |arg, i| {
|
||||
var val = self.lowerExpr(arg);
|
||||
var source_ty = self.inferExprType(arg);
|
||||
// If AST-based inference falls back to .s64 but the lowered ref is a string/struct, use that
|
||||
// If AST-based inference falls back to .i64 but the lowered ref is a string/struct, use that
|
||||
if (source_ty == .unresolved) {
|
||||
const ref_ty = self.builder.getRefType(val);
|
||||
if (ref_ty == .string or ref_ty == .f32 or ref_ty == .f64 or ref_ty == .bool) {
|
||||
@@ -270,7 +270,7 @@ pub fn lowerVariadicArgs(self: *Lowering, param_name: []const u8, call_args: []c
|
||||
}
|
||||
const boxed = if (source_ty == .any) val else self.builder.boxAny(val, source_ty);
|
||||
// GEP to array[i] and store
|
||||
const idx_ref = self.builder.constInt(@intCast(i), .s64);
|
||||
const idx_ref = self.builder.constInt(@intCast(i), .i64);
|
||||
const elem_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = idx_ref } }, self.module.types.ptrTo(.any));
|
||||
self.builder.store(elem_ptr, boxed);
|
||||
}
|
||||
@@ -278,13 +278,13 @@ pub fn lowerVariadicArgs(self: *Lowering, param_name: []const u8, call_args: []c
|
||||
// Build slice {ptr_to_first_element, len}
|
||||
const slice_slot = self.builder.alloca(any_slice_ty);
|
||||
// Get pointer to first element (array_slot is *[N x Any], GEP to element 0 gives *Any)
|
||||
const zero = self.builder.constInt(0, .s64);
|
||||
const zero = self.builder.constInt(0, .i64);
|
||||
const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = zero } }, self.module.types.ptrTo(.any));
|
||||
const len_ref = self.builder.constInt(@intCast(n), .s64);
|
||||
const len_ref = self.builder.constInt(@intCast(n), .i64);
|
||||
// Store into slice fields
|
||||
const ptr_gep = self.builder.structGepTyped(slice_slot, 0, self.module.types.ptrTo(.any), any_slice_ty);
|
||||
self.builder.store(ptr_gep, data_ptr);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, any_slice_ty);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, any_slice_ty);
|
||||
self.builder.store(len_gep, len_ref);
|
||||
|
||||
if (self.scope) |scope| {
|
||||
@@ -351,11 +351,11 @@ pub fn packVariadicCallArgs(self: *Lowering, fd: *const ast.FnDecl, c: *const as
|
||||
if (variadic_count == 0) {
|
||||
// Empty slice
|
||||
const null_ptr = self.builder.constNull(self.module.types.ptrTo(elem_ty));
|
||||
const zero_len = self.builder.constInt(0, .s64);
|
||||
const zero_len = self.builder.constInt(0, .i64);
|
||||
const slice_slot = self.builder.alloca(slice_ty);
|
||||
const ptr_gep = self.builder.structGepTyped(slice_slot, 0, self.module.types.ptrTo(elem_ty), slice_ty);
|
||||
self.builder.store(ptr_gep, null_ptr);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, slice_ty);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, slice_ty);
|
||||
self.builder.store(len_gep, zero_len);
|
||||
const slice_val = self.builder.load(slice_slot, slice_ty);
|
||||
// Replace args: keep fixed args, append slice
|
||||
@@ -385,7 +385,7 @@ pub fn packVariadicCallArgs(self: *Lowering, fd: *const ast.FnDecl, c: *const as
|
||||
var val = args.items[fixed_count + i];
|
||||
if (is_any) {
|
||||
var source_ty = self.inferExprType(c.args[fixed_count + i]);
|
||||
// If AST-based inference falls back to .s64 but the lowered ref has a richer type, use that
|
||||
// If AST-based inference falls back to .i64 but the lowered ref has a richer type, use that
|
||||
if (source_ty == .unresolved) {
|
||||
const ref_ty = self.builder.getRefType(val);
|
||||
if (ref_ty != .unresolved and ref_ty != .void) source_ty = ref_ty;
|
||||
@@ -432,19 +432,19 @@ pub fn packVariadicCallArgs(self: *Lowering, fd: *const ast.FnDecl, c: *const as
|
||||
val = self.buildProtocolErasure(val, arg_node, source_ty, elem_ty);
|
||||
}
|
||||
}
|
||||
const idx_ref = self.builder.constInt(@intCast(i), .s64);
|
||||
const idx_ref = self.builder.constInt(@intCast(i), .i64);
|
||||
const elem_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = idx_ref } }, self.module.types.ptrTo(array_elem));
|
||||
self.builder.store(elem_ptr, val);
|
||||
}
|
||||
|
||||
// Build slice {ptr, len}
|
||||
const slice_slot = self.builder.alloca(slice_ty);
|
||||
const zero = self.builder.constInt(0, .s64);
|
||||
const zero = self.builder.constInt(0, .i64);
|
||||
const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = zero } }, self.module.types.ptrTo(array_elem));
|
||||
const len_ref = self.builder.constInt(@intCast(variadic_count), .s64);
|
||||
const len_ref = self.builder.constInt(@intCast(variadic_count), .i64);
|
||||
const ptr_gep = self.builder.structGepTyped(slice_slot, 0, self.module.types.ptrTo(array_elem), slice_ty);
|
||||
self.builder.store(ptr_gep, data_ptr);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, slice_ty);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, slice_ty);
|
||||
self.builder.store(len_gep, len_ref);
|
||||
const slice_val = self.builder.load(slice_slot, slice_ty);
|
||||
|
||||
@@ -476,11 +476,11 @@ pub fn buildPackSliceValue(self: *Lowering, arg_types: []const TypeId) Ref {
|
||||
|
||||
if (arg_types.len == 0) {
|
||||
const null_ptr = self.builder.constNull(any_ptr_ty);
|
||||
const zero_len = self.builder.constInt(0, .s64);
|
||||
const zero_len = self.builder.constInt(0, .i64);
|
||||
const slice_slot = self.builder.alloca(any_slice_ty);
|
||||
const ptr_gep = self.builder.structGepTyped(slice_slot, 0, any_ptr_ty, any_slice_ty);
|
||||
self.builder.store(ptr_gep, null_ptr);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, any_slice_ty);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, any_slice_ty);
|
||||
self.builder.store(len_gep, zero_len);
|
||||
return self.builder.load(slice_slot, any_slice_ty);
|
||||
}
|
||||
@@ -493,18 +493,18 @@ pub fn buildPackSliceValue(self: *Lowering, arg_types: []const TypeId) Ref {
|
||||
// (`{tag=.any, value=tid}`) — already the canonical Any
|
||||
// shape, so no re-box needed.
|
||||
const type_val = self.builder.constType(ty);
|
||||
const idx_ref = self.builder.constInt(@intCast(i), .s64);
|
||||
const idx_ref = self.builder.constInt(@intCast(i), .i64);
|
||||
const elem_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = idx_ref } }, any_ptr_ty);
|
||||
self.builder.store(elem_ptr, type_val);
|
||||
}
|
||||
|
||||
const slice_slot = self.builder.alloca(any_slice_ty);
|
||||
const zero = self.builder.constInt(0, .s64);
|
||||
const zero = self.builder.constInt(0, .i64);
|
||||
const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = zero } }, any_ptr_ty);
|
||||
const len_ref = self.builder.constInt(@intCast(arg_types.len), .s64);
|
||||
const len_ref = self.builder.constInt(@intCast(arg_types.len), .i64);
|
||||
const ptr_gep = self.builder.structGepTyped(slice_slot, 0, any_ptr_ty, any_slice_ty);
|
||||
self.builder.store(ptr_gep, data_ptr);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, any_slice_ty);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, any_slice_ty);
|
||||
self.builder.store(len_gep, len_ref);
|
||||
return self.builder.load(slice_slot, any_slice_ty);
|
||||
}
|
||||
@@ -521,11 +521,11 @@ pub fn materialisePackSlice(
|
||||
|
||||
if (arg_types.len == 0) {
|
||||
const null_ptr = self.builder.constNull(any_ptr_ty);
|
||||
const zero_len = self.builder.constInt(0, .s64);
|
||||
const zero_len = self.builder.constInt(0, .i64);
|
||||
const slice_slot = self.builder.alloca(any_slice_ty);
|
||||
const ptr_gep = self.builder.structGepTyped(slice_slot, 0, any_ptr_ty, any_slice_ty);
|
||||
self.builder.store(ptr_gep, null_ptr);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, any_slice_ty);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, any_slice_ty);
|
||||
self.builder.store(len_gep, zero_len);
|
||||
scope.put(pack_name, .{ .ref = slice_slot, .ty = any_slice_ty, .is_alloca = true });
|
||||
return;
|
||||
@@ -537,18 +537,18 @@ pub fn materialisePackSlice(
|
||||
for (slot_refs, arg_types, 0..) |slot, ty, i| {
|
||||
const val = self.builder.load(slot, ty);
|
||||
const boxed = if (ty == .any) val else self.builder.boxAny(val, ty);
|
||||
const idx_ref = self.builder.constInt(@intCast(i), .s64);
|
||||
const idx_ref = self.builder.constInt(@intCast(i), .i64);
|
||||
const elem_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = idx_ref } }, any_ptr_ty);
|
||||
self.builder.store(elem_ptr, boxed);
|
||||
}
|
||||
|
||||
const slice_slot = self.builder.alloca(any_slice_ty);
|
||||
const zero = self.builder.constInt(0, .s64);
|
||||
const zero = self.builder.constInt(0, .i64);
|
||||
const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = zero } }, any_ptr_ty);
|
||||
const len_ref = self.builder.constInt(@intCast(arg_types.len), .s64);
|
||||
const len_ref = self.builder.constInt(@intCast(arg_types.len), .i64);
|
||||
const ptr_gep = self.builder.structGepTyped(slice_slot, 0, any_ptr_ty, any_slice_ty);
|
||||
self.builder.store(ptr_gep, data_ptr);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, any_slice_ty);
|
||||
const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, any_slice_ty);
|
||||
self.builder.store(len_gep, len_ref);
|
||||
scope.put(pack_name, .{ .ref = slice_slot, .ty = any_slice_ty, .is_alloca = true });
|
||||
}
|
||||
@@ -558,8 +558,8 @@ pub fn materialisePackSlice(
|
||||
/// type: a `return X;` statement's value type, or — failing that —
|
||||
/// the tail expression of an arrow-form body. Caller must have
|
||||
/// `pack_arg_nodes` installed so `args[<lit>]` substitutes during
|
||||
/// inference. Falls back to `.s64` if nothing concrete is found
|
||||
/// (matches the broader "default to .s64" convention elsewhere).
|
||||
/// inference. Falls back to `.i64` if nothing concrete is found
|
||||
/// (matches the broader "default to .i64" convention elsewhere).
|
||||
pub fn inferPackBodyReturnType(self: *Lowering, body: *const Node) TypeId {
|
||||
// First try explicit `return X;` — walks past structured
|
||||
// control flow but stops at nested fn / lambda bodies.
|
||||
|
||||
@@ -67,10 +67,10 @@ pub fn registerProtocolDecl(self: *Lowering, pd: *const ast.ProtocolDecl) void {
|
||||
}
|
||||
|
||||
/// Instantiate a parameterized protocol as a runtime VALUE type:
|
||||
/// `VL(s64)` → a 16-byte `{ctx, __vtable}` protocol value (`is_protocol`),
|
||||
/// `VL(i64)` → a 16-byte `{ctx, __vtable}` protocol value (`is_protocol`),
|
||||
/// with method infos resolved under the type-arg binding (so `get -> T`
|
||||
/// becomes `get -> s64`) and the binding recorded for projection. Cached by
|
||||
/// the mangled name `VL__s64`. Mirrors the non-parameterized path in
|
||||
/// becomes `get -> i64`) and the binding recorded for projection. Cached by
|
||||
/// the mangled name `VL__i64`. Mirrors the non-parameterized path in
|
||||
/// `registerProtocolDecl`.
|
||||
pub fn instantiateParamProtocol(self: *Lowering, pd: *const ast.ProtocolDecl, args: []const *const Node) TypeId {
|
||||
const table = &self.module.types;
|
||||
@@ -105,7 +105,7 @@ pub fn instantiateParamProtocol(self: *Lowering, pd: *const ast.ProtocolDecl, ar
|
||||
const id = if (table.findByName(name_id)) |existing| existing else table.intern(struct_info);
|
||||
table.updatePreservingKey(id, struct_info);
|
||||
|
||||
// Method infos resolved with the type-arg binding (T → s64), pinned to
|
||||
// Method infos resolved with the type-arg binding (T → i64), pinned to
|
||||
// the protocol's OWN module (E4) so a method-signature type visible only
|
||||
// there resolves correctly when instantiated cross-module. `Self` and the
|
||||
// bound type-args short-circuit before the leaf; a concrete library type
|
||||
@@ -332,10 +332,10 @@ pub fn createProtocolThunk(self: *Lowering, proto_name: []const u8, concrete_typ
|
||||
if (self.program_index.fn_ast_map.contains(qualified)) {
|
||||
self.lazyLowerFunction(qualified);
|
||||
} else if (self.genericInstanceMethod(concrete_type_name, method.name)) |gm| {
|
||||
// Generic-struct instance (`Combined__s64_s64`): the impl method is
|
||||
// Generic-struct instance (`Combined__i64_i64`): the impl method is
|
||||
// authored on the instance's STAMPED decl (CP-4). Monomorphize it
|
||||
// for this instance's bindings so the thunk has a concrete
|
||||
// `Combined__s64_s64.get` to call.
|
||||
// `Combined__i64_i64.get` to call.
|
||||
self.monomorphizeFunction(gm.fd, qualified, gm.bindings);
|
||||
}
|
||||
}
|
||||
@@ -441,7 +441,7 @@ pub fn buildProtocolValue(self: *Lowering, concrete_ptr: Ref, proto_name: []cons
|
||||
var ctx_ptr = concrete_ptr;
|
||||
if (heap_copy) {
|
||||
const concrete_size = self.module.types.typeSizeBytes(concrete_ty);
|
||||
const size_ref = self.builder.constInt(@intCast(concrete_size), .s64);
|
||||
const size_ref = self.builder.constInt(@intCast(concrete_size), .i64);
|
||||
const heap_ptr = self.allocViaContext(size_ref, void_ptr_ty);
|
||||
_ = self.callForeign("memcpy", &.{ heap_ptr, concrete_ptr, size_ref }, void_ptr_ty);
|
||||
ctx_ptr = heap_ptr;
|
||||
@@ -591,7 +591,7 @@ pub fn emitProtocolDispatch(self: *Lowering, receiver: Ref, proto_info: Protocol
|
||||
/// Handles both direct types and pointer-to-types.
|
||||
pub fn resolveConcreteTypeName(self: *Lowering, ty: TypeId) ?[]const u8 {
|
||||
if (ty.isBuiltin()) {
|
||||
// Primitive types like s64 — check if they have toName()
|
||||
// Primitive types like i64 — check if they have toName()
|
||||
return self.module.types.typeName(ty);
|
||||
}
|
||||
const info = self.module.types.get(ty);
|
||||
|
||||
@@ -329,7 +329,7 @@ pub fn lowerVarDecl(self: *Lowering, vd: *const ast.VarDecl) void {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Coerce value to match target type (e.g. u8 → s64 widening)
|
||||
// Coerce value to match target type (e.g. u8 → i64 widening)
|
||||
{
|
||||
const ref_ty = self.builder.getRefType(ref);
|
||||
if (ref_ty != ty and ref_ty != .void and ty != .void) {
|
||||
@@ -354,7 +354,7 @@ pub fn lowerVarDecl(self: *Lowering, vd: *const ast.VarDecl) void {
|
||||
self.force_block_value = true;
|
||||
// An unannotated decl provides no target type: clear the ambient one
|
||||
// (the enclosing fn's implicit-return target) so literal initializers
|
||||
// take their spec defaults (s64/f64) instead of adopting it.
|
||||
// take their spec defaults (i64/f64) instead of adopting it.
|
||||
self.target_type = null;
|
||||
const ref = self.lowerExpr(val);
|
||||
self.force_block_value = saved_fbv;
|
||||
@@ -366,7 +366,7 @@ pub fn lowerVarDecl(self: *Lowering, vd: *const ast.VarDecl) void {
|
||||
scope.put(vd.name, .{ .ref = slot, .ty = ty, .is_alloca = true });
|
||||
}
|
||||
} else {
|
||||
const ty = TypeId.s64;
|
||||
const ty = TypeId.i64;
|
||||
const slot = self.builder.alloca(ty);
|
||||
self.builder.store(slot, self.zeroValue(ty));
|
||||
if (self.scope) |scope| {
|
||||
@@ -390,7 +390,7 @@ pub fn lowerLocalFnDecl(self: *Lowering, fd: *const ast.FnDecl) void {
|
||||
}
|
||||
|
||||
pub fn lowerConstDecl(self: *Lowering, cd: *const ast.ConstDecl) void {
|
||||
// Handle local function declarations: fx :: (s:s3) -> s3 { ... }
|
||||
// Handle local function declarations: fx :: (s:i3) -> i3 { ... }
|
||||
if (cd.value.data == .fn_decl) {
|
||||
const fd = &cd.value.data.fn_decl;
|
||||
// Use mangled name for local functions to support block-scoped shadowing
|
||||
@@ -448,15 +448,15 @@ pub fn lowerReturn(self: *Lowering, rs: *const ast.ReturnStmt) void {
|
||||
}
|
||||
// Set target_type to function return type so null_literal etc. get the right type.
|
||||
// When inlining a comptime body, the *inlined* fn's declared return type wins
|
||||
// over the caller's — otherwise `return 42` inside a `-> s64` body lowered into
|
||||
// a `-> s32` caller would coerce 42 to s32 before storing into the s64 slot.
|
||||
// over the caller's — otherwise `return 42` inside a `-> i64` body lowered into
|
||||
// a `-> i32` caller would coerce 42 to i32 before storing into the i64 slot.
|
||||
const old_target = self.target_type;
|
||||
const ret_ty_for_target: TypeId = if (self.inline_return_target) |iri|
|
||||
iri.ret_ty
|
||||
else if (self.builder.func) |fid|
|
||||
self.module.functions.items[@intFromEnum(fid)].ret
|
||||
else
|
||||
TypeId.s64;
|
||||
TypeId.i64;
|
||||
// A value-carrying failable (`-> (T..., !)`) returns its VALUE part and
|
||||
// the success error slot (0) is appended by lowerFailableSuccessReturn.
|
||||
// Resolve a BARE returned value against that value type, NOT the failable
|
||||
@@ -523,7 +523,7 @@ pub fn lowerReturn(self: *Lowering, rs: *const ast.ReturnStmt) void {
|
||||
const ret_ty = if (self.builder.func) |fid|
|
||||
self.module.functions.items[@intFromEnum(fid)].ret
|
||||
else
|
||||
TypeId.s64;
|
||||
TypeId.i64;
|
||||
if (ret_ty == .void) {
|
||||
// Void function — just return void (the value expression was evaluated for side effects)
|
||||
self.builder.retVoid();
|
||||
@@ -532,7 +532,7 @@ pub fn lowerReturn(self: *Lowering, rs: *const ast.ReturnStmt) void {
|
||||
// value part; the compiler appends the success error slot (0).
|
||||
self.lowerFailableSuccessReturn(ref, ret_ty, rs.value.?.span);
|
||||
} else {
|
||||
// Coerce return value to match function return type (e.g., ?s32 → s32)
|
||||
// Coerce return value to match function return type (e.g., ?i32 → i32)
|
||||
const val_ty = self.builder.getRefType(ref);
|
||||
const coerced = self.coerceToType(ref, val_ty, ret_ty);
|
||||
self.builder.ret(coerced, ret_ty);
|
||||
@@ -748,11 +748,11 @@ pub fn lowerAssignment(self: *Lowering, asgn: *const ast.Assignment) void {
|
||||
} else false);
|
||||
|
||||
if (is_special_container and std.mem.eql(u8, fa.field, "len")) {
|
||||
const gep = self.builder.structGepTyped(obj_ptr, 1, .s64, obj_ty);
|
||||
self.storeOrCompound(gep, val, asgn.op, .s64);
|
||||
const gep = self.builder.structGepTyped(obj_ptr, 1, .i64, obj_ty);
|
||||
self.storeOrCompound(gep, val, asgn.op, .i64);
|
||||
} else if (is_special_container and std.mem.eql(u8, fa.field, "ptr")) {
|
||||
const gep = self.builder.structGepTyped(obj_ptr, 0, .s64, obj_ty);
|
||||
self.storeOrCompound(gep, val, asgn.op, .s64);
|
||||
const gep = self.builder.structGepTyped(obj_ptr, 0, .i64, obj_ty);
|
||||
self.storeOrCompound(gep, val, asgn.op, .i64);
|
||||
} else if (self.fieldLvaluePtr(obj_ptr, obj_ty, fa.field)) |fl| {
|
||||
// Resolve the target field (struct / union direct / promoted
|
||||
// anonymous-struct member / tuple element / vector lane) via
|
||||
@@ -986,7 +986,7 @@ pub fn lowerExprAsPtr(self: *Lowering, node: *const Node) Ref {
|
||||
// resolver so address-of and the multi-target store path never
|
||||
// disagree on the slot. No match → emit the read path's
|
||||
// field-not-found diagnostic (lowerFieldAccessOnType →
|
||||
// emitFieldError) instead of silently GEPing field 0 as .s64;
|
||||
// emitFieldError) instead of silently GEPing field 0 as .i64;
|
||||
// that bogus pointer reaches LLVM emission as ptrTo(.unresolved)
|
||||
// and panics.
|
||||
if (self.fieldLvaluePtr(obj_ptr, obj_ty, fa.field)) |r| return r.ptr;
|
||||
|
||||
@@ -13,7 +13,7 @@ const GlobalId = inst_mod.GlobalId;
|
||||
const Module = mod_mod.Module;
|
||||
const Builder = mod_mod.Builder;
|
||||
|
||||
test "Builder: build add(a: s64, b: s64) -> s64" {
|
||||
test "Builder: build add(a: i64, b: i64) -> i64" {
|
||||
const alloc = std.testing.allocator;
|
||||
var mod = Module.init(alloc);
|
||||
defer mod.deinit();
|
||||
@@ -26,26 +26,26 @@ test "Builder: build add(a: s64, b: s64) -> s64" {
|
||||
const name_entry = mod.types.internString("entry");
|
||||
|
||||
const params = &[_]Function.Param{
|
||||
.{ .name = name_a, .ty = .s64 },
|
||||
.{ .name = name_b, .ty = .s64 },
|
||||
.{ .name = name_a, .ty = .i64 },
|
||||
.{ .name = name_b, .ty = .i64 },
|
||||
};
|
||||
const func_id = b.beginFunction(name_add, params, .s64);
|
||||
const func_id = b.beginFunction(name_add, params, .i64);
|
||||
|
||||
const entry = b.appendBlock(name_entry, &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
// Load params (in real lowering, params are block params of entry)
|
||||
const a_ref = b.constInt(0, .s64); // placeholder for param a
|
||||
const b_ref = b.constInt(0, .s64); // placeholder for param b
|
||||
const sum = b.add(a_ref, b_ref, .s64);
|
||||
b.ret(sum, .s64);
|
||||
const a_ref = b.constInt(0, .i64); // placeholder for param a
|
||||
const b_ref = b.constInt(0, .i64); // placeholder for param b
|
||||
const sum = b.add(a_ref, b_ref, .i64);
|
||||
b.ret(sum, .i64);
|
||||
|
||||
b.finalize();
|
||||
|
||||
// Verify
|
||||
const func = mod.getFunction(func_id);
|
||||
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.expectEqual(@as(usize, 1), func.blocks.items.len);
|
||||
|
||||
const blk = &func.blocks.items[0];
|
||||
@@ -65,28 +65,28 @@ test "Builder: conditional branch" {
|
||||
const name_else = mod.types.internString("else");
|
||||
const name_merge = mod.types.internString("merge");
|
||||
|
||||
_ = b.beginFunction(name_fn, &.{}, .s32);
|
||||
_ = b.beginFunction(name_fn, &.{}, .i32);
|
||||
|
||||
const entry = b.appendBlock(name_entry, &.{});
|
||||
const then_bb = b.appendBlock(name_then, &.{});
|
||||
const else_bb = b.appendBlock(name_else, &.{});
|
||||
const merge_bb = b.appendBlock(name_merge, &[_]TypeId{.s32});
|
||||
const merge_bb = b.appendBlock(name_merge, &[_]TypeId{.i32});
|
||||
|
||||
b.switchToBlock(entry);
|
||||
const cond = b.constBool(true);
|
||||
b.condBr(cond, then_bb, &.{}, else_bb, &.{});
|
||||
|
||||
b.switchToBlock(then_bb);
|
||||
const v1 = b.constInt(42, .s32);
|
||||
const v1 = b.constInt(42, .i32);
|
||||
b.br(merge_bb, &.{v1});
|
||||
|
||||
b.switchToBlock(else_bb);
|
||||
const v2 = b.constInt(0, .s32);
|
||||
const v2 = b.constInt(0, .i32);
|
||||
b.br(merge_bb, &.{v2});
|
||||
|
||||
b.switchToBlock(merge_bb);
|
||||
const result = b.emit(.{ .block_param = .{ .block = merge_bb, .param_index = 0 } }, .s32);
|
||||
b.ret(result, .s32);
|
||||
const result = b.emit(.{ .block_param = .{ .block = merge_bb, .param_index = 0 } }, .i32);
|
||||
b.ret(result, .i32);
|
||||
|
||||
b.finalize();
|
||||
|
||||
@@ -107,12 +107,12 @@ test "Module: globals" {
|
||||
const name = mod.types.internString("counter");
|
||||
const id = mod.addGlobal(.{
|
||||
.name = name,
|
||||
.ty = .s32,
|
||||
.ty = .i32,
|
||||
.init_val = .{ .int = 0 },
|
||||
});
|
||||
|
||||
try std.testing.expectEqual(GlobalId.fromIndex(0), id);
|
||||
try std.testing.expectEqual(TypeId.s32, mod.globals.items[0].ty);
|
||||
try std.testing.expectEqual(TypeId.i32, mod.globals.items[0].ty);
|
||||
}
|
||||
|
||||
test "Builder.constFloatInfo reads a const_float back, null for non-floats" {
|
||||
@@ -134,7 +134,7 @@ test "Builder.constFloatInfo reads a const_float back, null for non-floats" {
|
||||
try std.testing.expectEqual(@as(f64, 4.0), info.value);
|
||||
|
||||
// A non-float instruction is not a const_float — null.
|
||||
const iref = b.constInt(7, .s64);
|
||||
const iref = b.constInt(7, .i64);
|
||||
try std.testing.expect(b.constFloatInfo(iref) == null);
|
||||
|
||||
b.finalize();
|
||||
|
||||
@@ -336,7 +336,7 @@ pub const Builder = struct {
|
||||
|
||||
/// Get the type of a previously emitted instruction Ref. A ref that can't
|
||||
/// be located (no active function, or an out-of-range ref) has no knowable
|
||||
/// type — return the `.unresolved` sentinel rather than a fabricated `.s64`.
|
||||
/// type — return the `.unresolved` sentinel rather than a fabricated `.i64`.
|
||||
pub fn getRefType(self: *Builder, ref: Ref) TypeId {
|
||||
if (self.func == null) return .unresolved;
|
||||
const func = self.currentFunc();
|
||||
|
||||
@@ -16,7 +16,7 @@ test "PackResolver.packTypeArgs: bound pack → element types; unbound → null"
|
||||
|
||||
var pat = std.StringHashMap([]const TypeId).init(alloc);
|
||||
defer pat.deinit();
|
||||
const elems = [_]TypeId{ .s32, .s64 };
|
||||
const elems = [_]TypeId{ .i32, .i64 };
|
||||
try pat.put("xs", &elems);
|
||||
lowering.pack_arg_types = pat;
|
||||
|
||||
@@ -64,7 +64,7 @@ test "PackResolver.packTypeArgs: missing projection → diagnostic + .unresolved
|
||||
try lowering.program_index.protocol_ast_map.put("P", &pd);
|
||||
|
||||
var pat = std.StringHashMap([]const TypeId).init(alloc);
|
||||
const elems = [_]TypeId{.s64};
|
||||
const elems = [_]TypeId{.i64};
|
||||
try pat.put("xs", &elems);
|
||||
lowering.pack_arg_types = pat;
|
||||
|
||||
|
||||
@@ -26,17 +26,17 @@ test "print simple add function" {
|
||||
const name_entry = module.types.internString("entry");
|
||||
|
||||
const params = &[_]Function.Param{
|
||||
.{ .name = name_a, .ty = .s64 },
|
||||
.{ .name = name_b, .ty = .s64 },
|
||||
.{ .name = name_a, .ty = .i64 },
|
||||
.{ .name = name_b, .ty = .i64 },
|
||||
};
|
||||
_ = b.beginFunction(name_add, params, .s64);
|
||||
_ = b.beginFunction(name_add, params, .i64);
|
||||
const entry = b.appendBlock(name_entry, &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const a_ref = b.constInt(10, .s64);
|
||||
const b_ref = b.constInt(20, .s64);
|
||||
const sum = b.add(a_ref, b_ref, .s64);
|
||||
b.ret(sum, .s64);
|
||||
const a_ref = b.constInt(10, .i64);
|
||||
const b_ref = b.constInt(20, .i64);
|
||||
const sum = b.add(a_ref, b_ref, .i64);
|
||||
b.ret(sum, .i64);
|
||||
b.finalize();
|
||||
|
||||
var aw = std.Io.Writer.Allocating.init(alloc);
|
||||
@@ -45,11 +45,11 @@ test "print simple add function" {
|
||||
defer result.deinit(alloc);
|
||||
|
||||
const output = result.items;
|
||||
try std.testing.expect(std.mem.indexOf(u8, output, "func @add(a: s64, b: s64) -> s64") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, output, "func @add(a: i64, b: i64) -> i64") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, output, "entry:") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, output, "const 10 : s64") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, output, "const 10 : i64") != null);
|
||||
// Params occupy value slots %0/%1, so the two consts are %2/%3 and their sum %4.
|
||||
try std.testing.expect(std.mem.indexOf(u8, output, "add %2, %3 : s64") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, output, "add %2, %3 : i64") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, output, "ret %4") != null);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ test "print conditional branch" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(module.types.internString("test"), &.{}, .s32);
|
||||
_ = b.beginFunction(module.types.internString("test"), &.{}, .i32);
|
||||
const entry = b.appendBlock(module.types.internString("entry"), &.{});
|
||||
const then_bb = b.appendBlock(module.types.internString("then"), &.{});
|
||||
const else_bb = b.appendBlock(module.types.internString("else"), &.{});
|
||||
@@ -70,12 +70,12 @@ test "print conditional branch" {
|
||||
b.condBr(cond, then_bb, &.{}, else_bb, &.{});
|
||||
|
||||
b.switchToBlock(then_bb);
|
||||
const v1 = b.constInt(1, .s32);
|
||||
b.ret(v1, .s32);
|
||||
const v1 = b.constInt(1, .i32);
|
||||
b.ret(v1, .i32);
|
||||
|
||||
b.switchToBlock(else_bb);
|
||||
const v2 = b.constInt(0, .s32);
|
||||
b.ret(v2, .s32);
|
||||
const v2 = b.constInt(0, .i32);
|
||||
b.ret(v2, .i32);
|
||||
b.finalize();
|
||||
|
||||
var aw = std.Io.Writer.Allocating.init(alloc);
|
||||
|
||||
@@ -56,15 +56,15 @@ test "ProgramIndex declaration maps round-trip (A1.1b)" {
|
||||
try std.testing.expect(idx.fn_ast_map.get("main").? == &fd);
|
||||
|
||||
// type_alias_map: alias name → target TypeId.
|
||||
try idx.type_alias_map.put("ShaderHandle", .s64);
|
||||
try std.testing.expectEqual(@as(?types.TypeId, .s64), idx.type_alias_map.get("ShaderHandle"));
|
||||
try idx.type_alias_map.put("ShaderHandle", .i64);
|
||||
try std.testing.expectEqual(@as(?types.TypeId, .i64), idx.type_alias_map.get("ShaderHandle"));
|
||||
|
||||
// global_names: #run global name → GlobalInfo.
|
||||
try idx.global_names.put("g", .{ .id = inst.GlobalId.fromIndex(0), .ty = .s64 });
|
||||
try idx.global_names.put("g", .{ .id = inst.GlobalId.fromIndex(0), .ty = .i64 });
|
||||
try std.testing.expect(idx.global_names.get("g").?.id == inst.GlobalId.fromIndex(0));
|
||||
|
||||
// module_const_map: const name → ModuleConstInfo.
|
||||
try idx.module_const_map.put("AF_INET", .{ .value = &blk, .ty = .s32 });
|
||||
try idx.module_const_map.put("AF_INET", .{ .value = &blk, .ty = .i32 });
|
||||
try std.testing.expect(idx.module_const_map.get("AF_INET").?.value == &blk);
|
||||
|
||||
// foreign_class_map: sx alias → ForeignClassDecl.
|
||||
@@ -110,22 +110,22 @@ test "ProgramIndex source-keyed caches partition same-name authors by source" {
|
||||
var blk_b = ast.Node{ .span = .{ .start = 1, .end = 1 }, .data = .{ .block = .{ .stmts = &.{} } } };
|
||||
|
||||
// SAME alias name `Foo` authored in two modules → two distinct TypeIds.
|
||||
idx.putTypeAliasBySource("a.sx", "Foo", .s64);
|
||||
idx.putTypeAliasBySource("a.sx", "Foo", .i64);
|
||||
idx.putTypeAliasBySource("b.sx", "Foo", .f64);
|
||||
try std.testing.expectEqual(@as(?types.TypeId, .s64), idx.type_aliases_by_source.get("a.sx").?.get("Foo"));
|
||||
try std.testing.expectEqual(@as(?types.TypeId, .i64), idx.type_aliases_by_source.get("a.sx").?.get("Foo"));
|
||||
try std.testing.expectEqual(@as(?types.TypeId, .f64), idx.type_aliases_by_source.get("b.sx").?.get("Foo"));
|
||||
try std.testing.expectEqual(@as(u32, 2), idx.type_aliases_by_source.count());
|
||||
|
||||
// SAME const name `K` authored in two modules → two distinct ModuleConstInfos.
|
||||
idx.putModuleConstBySource("a.sx", "K", .{ .value = &blk_a, .ty = .s32 });
|
||||
idx.putModuleConstBySource("a.sx", "K", .{ .value = &blk_a, .ty = .i32 });
|
||||
idx.putModuleConstBySource("b.sx", "K", .{ .value = &blk_b, .ty = .f32 });
|
||||
try std.testing.expect(idx.module_consts_by_source.get("a.sx").?.get("K").?.value == &blk_a);
|
||||
try std.testing.expect(idx.module_consts_by_source.get("b.sx").?.get("K").?.value == &blk_b);
|
||||
try std.testing.expectEqual(@as(?types.TypeId, .s32), idx.module_consts_by_source.get("a.sx").?.get("K").?.ty);
|
||||
try std.testing.expectEqual(@as(?types.TypeId, .i32), idx.module_consts_by_source.get("a.sx").?.get("K").?.ty);
|
||||
try std.testing.expectEqual(@as(?types.TypeId, .f32), idx.module_consts_by_source.get("b.sx").?.get("K").?.ty);
|
||||
|
||||
// SAME global name `g` authored in two modules → two distinct GlobalInfos.
|
||||
idx.putGlobalBySource("a.sx", "g", .{ .id = inst.GlobalId.fromIndex(0), .ty = .s64 });
|
||||
idx.putGlobalBySource("a.sx", "g", .{ .id = inst.GlobalId.fromIndex(0), .ty = .i64 });
|
||||
idx.putGlobalBySource("b.sx", "g", .{ .id = inst.GlobalId.fromIndex(1), .ty = .f64 });
|
||||
try std.testing.expect(idx.globals_by_source.get("a.sx").?.get("g").?.id == inst.GlobalId.fromIndex(0));
|
||||
try std.testing.expect(idx.globals_by_source.get("b.sx").?.get("g").?.id == inst.GlobalId.fromIndex(1));
|
||||
@@ -133,10 +133,10 @@ test "ProgramIndex source-keyed caches partition same-name authors by source" {
|
||||
// Compat readers: the legacy global maps stay keyed by NAME alone, so a
|
||||
// same-name author is last-wins there — exactly ONE entry for `Foo` / `K`,
|
||||
// unchanged by the source-keyed writes above.
|
||||
idx.type_alias_map.put("Foo", .s64) catch unreachable;
|
||||
idx.type_alias_map.put("Foo", .i64) catch unreachable;
|
||||
idx.type_alias_map.put("Foo", .f64) catch unreachable;
|
||||
try std.testing.expectEqual(@as(u32, 1), idx.type_alias_map.count());
|
||||
idx.module_const_map.put("K", .{ .value = &blk_a, .ty = .s32 }) catch unreachable;
|
||||
idx.module_const_map.put("K", .{ .value = &blk_a, .ty = .i32 }) catch unreachable;
|
||||
idx.module_const_map.put("K", .{ .value = &blk_b, .ty = .f32 }) catch unreachable;
|
||||
try std.testing.expectEqual(@as(u32, 1), idx.module_const_map.count());
|
||||
|
||||
@@ -315,9 +315,9 @@ test "moduleConstInt folds expression-RHS consts and rejects cycles" {
|
||||
var f_val = nFloat(4.0);
|
||||
var g_val = nFloat(4.5);
|
||||
|
||||
try map.put("M", .{ .value = &m_val, .ty = .s64 });
|
||||
try map.put("N", .{ .value = &n_val, .ty = .s64 });
|
||||
try map.put("P", .{ .value = &p_val, .ty = .s64 });
|
||||
try map.put("M", .{ .value = &m_val, .ty = .i64 });
|
||||
try map.put("N", .{ .value = &n_val, .ty = .i64 });
|
||||
try map.put("P", .{ .value = &p_val, .ty = .i64 });
|
||||
try map.put("F", .{ .value = &f_val, .ty = .f64 });
|
||||
try map.put("G", .{ .value = &g_val, .ty = .f64 });
|
||||
|
||||
@@ -338,9 +338,9 @@ test "moduleConstInt folds expression-RHS consts and rejects cycles" {
|
||||
var a_val = nBin(.add, &b_id, &zero);
|
||||
var b_val = nBin(.add, &a_id, &zero);
|
||||
var c_val = nBin(.add, &c_id, &zero);
|
||||
try map.put("A", .{ .value = &a_val, .ty = .s64 });
|
||||
try map.put("B", .{ .value = &b_val, .ty = .s64 });
|
||||
try map.put("C", .{ .value = &c_val, .ty = .s64 });
|
||||
try map.put("A", .{ .value = &a_val, .ty = .i64 });
|
||||
try map.put("B", .{ .value = &b_val, .ty = .i64 });
|
||||
try map.put("C", .{ .value = &c_val, .ty = .i64 });
|
||||
try std.testing.expect(pi.moduleConstInt(&map, &table, "A") == null);
|
||||
try std.testing.expect(pi.moduleConstInt(&map, &table, "B") == null);
|
||||
try std.testing.expect(pi.moduleConstInt(&map, &table, "C") == null);
|
||||
@@ -354,7 +354,7 @@ test "moduleConstIsFloatTyped judges a const by VALUE, catching untyped float-EX
|
||||
|
||||
// KT : f64 : 4.0 (typed float), MI :: 2 (untyped int), ML :: 5.0 (untyped
|
||||
// float literal → f64), ME :: 4.0 + 1.0 (untyped float EXPRESSION, placeholder
|
||||
// type s64 yet float-valued), IE :: 1 + 2 (untyped int expression).
|
||||
// type i64 yet float-valued), IE :: 1 + 2 (untyped int expression).
|
||||
var kt_val = nFloat(4.0);
|
||||
var mi_val = nLit(2);
|
||||
var ml_val = nFloat(5.0);
|
||||
@@ -365,13 +365,13 @@ test "moduleConstIsFloatTyped judges a const by VALUE, catching untyped float-EX
|
||||
var l2 = nLit(2);
|
||||
var ie_val = nBin(.add, &l1, &l2);
|
||||
try map.put("KT", .{ .value = &kt_val, .ty = .f64 });
|
||||
try map.put("MI", .{ .value = &mi_val, .ty = .s64 });
|
||||
try map.put("MI", .{ .value = &mi_val, .ty = .i64 });
|
||||
try map.put("ML", .{ .value = &ml_val, .ty = .f64 }); // pass-0 stores a float literal as f64
|
||||
try map.put("ME", .{ .value = &me_val, .ty = .s64 }); // pass-0 placeholder for a binary_op
|
||||
try map.put("IE", .{ .value = &ie_val, .ty = .s64 });
|
||||
try map.put("ME", .{ .value = &me_val, .ty = .i64 }); // pass-0 placeholder for a binary_op
|
||||
try map.put("IE", .{ .value = &ie_val, .ty = .i64 });
|
||||
|
||||
// Float-valued: a typed float const, an untyped float literal, AND an untyped
|
||||
// float EXPRESSION whose declared type is the s64 placeholder (judged by value).
|
||||
// float EXPRESSION whose declared type is the i64 placeholder (judged by value).
|
||||
try std.testing.expect(pi.moduleConstIsFloatTyped(&map, &table, "KT"));
|
||||
try std.testing.expect(pi.moduleConstIsFloatTyped(&map, &table, "ML"));
|
||||
try std.testing.expect(pi.moduleConstIsFloatTyped(&map, &table, "ME"));
|
||||
@@ -386,8 +386,8 @@ test "moduleConstIsFloatTyped judges a const by VALUE, catching untyped float-EX
|
||||
var az = nFloat(0.0);
|
||||
var a_val = nBin(.add, &b_id, &az);
|
||||
var b_val = nBin(.add, &a_id, &az);
|
||||
try map.put("A", .{ .value = &a_val, .ty = .s64 });
|
||||
try map.put("B", .{ .value = &b_val, .ty = .s64 });
|
||||
try map.put("A", .{ .value = &a_val, .ty = .i64 });
|
||||
try map.put("B", .{ .value = &b_val, .ty = .i64 });
|
||||
// The `+ 0.0` literal still makes them float-valued (a finite, non-cyclic leaf
|
||||
// is reached before the cycle); the point is it TERMINATES.
|
||||
try std.testing.expect(pi.moduleConstIsFloatTyped(&map, &table, "A"));
|
||||
@@ -404,7 +404,7 @@ test "moduleConstInt gates the fold on the declared type, not the initializer no
|
||||
// initializer must never be folded into a count: the count path
|
||||
// consults `ModuleConstInfo.ty`, not just the node shape.
|
||||
var int_val = nLit(4);
|
||||
try map.put("OK", .{ .value = &int_val, .ty = .s64 });
|
||||
try map.put("OK", .{ .value = &int_val, .ty = .i64 });
|
||||
try map.put("STR", .{ .value = &int_val, .ty = .string });
|
||||
try map.put("BOOLEAN", .{ .value = &int_val, .ty = .bool });
|
||||
|
||||
@@ -415,12 +415,12 @@ test "moduleConstInt gates the fold on the declared type, not the initializer no
|
||||
// The same gate holds for a const-EXPRESSION value node (`M + 2`), not just
|
||||
// a bare literal: a `string`-typed const whose initializer is a foldable
|
||||
// integer expression must still never fold as a count (the
|
||||
// const-expression leak). `KEXPR : s64 : M + 2` (numeric type) folds; the
|
||||
// const-expression leak). `KEXPR : i64 : M + 2` (numeric type) folds; the
|
||||
// same expression declared `string` does not.
|
||||
var m_lit = nLit(2);
|
||||
var add2 = nLit(2);
|
||||
var expr_val = nBin(.add, &m_lit, &add2);
|
||||
try map.put("KEXPR", .{ .value = &expr_val, .ty = .s64 });
|
||||
try map.put("KEXPR", .{ .value = &expr_val, .ty = .i64 });
|
||||
try map.put("STREXPR", .{ .value = &expr_val, .ty = .string });
|
||||
try std.testing.expectEqual(@as(?i64, 4), pi.moduleConstInt(&map, &table, "KEXPR"));
|
||||
try std.testing.expect(pi.moduleConstInt(&map, &table, "STREXPR") == null);
|
||||
@@ -539,21 +539,21 @@ test "a backtick raw-shadow receiver is a field read, not a numeric-limit fold (
|
||||
const ctx = DimCtx{};
|
||||
|
||||
// BARE type receiver (`is_raw = false`) → the numeric-limit accessor folds:
|
||||
// `f64.epsilon` is the builtin eps, `s8.max` is 127.
|
||||
// `f64.epsilon` is the builtin eps, `i8.max` is 127.
|
||||
var f64ty = nIdent("f64");
|
||||
var s8ty = nIdent("s8");
|
||||
var s8ty = nIdent("i8");
|
||||
var bare_feps = nField(&f64ty, "epsilon");
|
||||
var bare_smax = nField(&s8ty, "max");
|
||||
try std.testing.expectEqual(@as(?f64, @as(f64, std.math.floatEps(f64))), evalf(&bare_feps, ctx));
|
||||
try std.testing.expectEqual(@as(?i64, std.math.maxInt(i8)), evali(&bare_smax, ctx));
|
||||
|
||||
// RAW receiver (`` `f64 ``/`` `s8 ``) shadows the builtin with a VALUE — the
|
||||
// RAW receiver (`` `f64 ``/`` `i8 ``) shadows the builtin with a VALUE — the
|
||||
// field access is an ordinary runtime field READ, so it is NOT a compile-time
|
||||
// leaf in either evaluator (→ null), exactly as the sibling `isFloatValuedExpr`
|
||||
// already treats it. The whole point: a value-shadow can never be misread as
|
||||
// the builtin limit.
|
||||
var f64raw = nIdentRaw("f64");
|
||||
var s8raw = nIdentRaw("s8");
|
||||
var s8raw = nIdentRaw("i8");
|
||||
var raw_feps = nField(&f64raw, "epsilon");
|
||||
var raw_smax = nField(&s8raw, "max");
|
||||
try std.testing.expect(evalf(&raw_feps, ctx) == null);
|
||||
|
||||
@@ -150,7 +150,7 @@ pub fn isFloatConstType(ty: TypeId) bool {
|
||||
/// True iff `name` is a FLOAT-valued module const — judged by the const's VALUE,
|
||||
/// not only its DECLARED type, so it catches both a typed float const
|
||||
/// (`K : f64 : 4.0`, `F : f64 : 2.5`) AND an UNTYPED float-EXPRESSION const
|
||||
/// (`ME :: 4.0 + 1.0`), whose pass-0 placeholder type is `s64` even though its
|
||||
/// (`ME :: 4.0 + 1.0`), whose pass-0 placeholder type is `i64` even though its
|
||||
/// value is float. The int folder's division arm consults this to tell a FLOAT
|
||||
/// division apart from an integer one even when both operands fold to integers
|
||||
/// (`K / 3`, `ME / 3`). `frame` cycle-guards a const whose value references
|
||||
@@ -170,11 +170,11 @@ fn moduleConstFloatValuedFramed(consts: *const std.StringHashMap(ModuleConstInfo
|
||||
/// is gated on `ModuleConstInfo.ty`, not just the shape of the initializer node:
|
||||
/// a `string`/`bool`/pointer/struct-typed const can never be folded into a count
|
||||
/// off an integer-looking initializer (the second symptom, where
|
||||
/// `N : string : 4` folded `[N]s64` to 4 by reading the `int_literal` node and
|
||||
/// `N : string : 4` folded `[N]i64` to 4 by reading the `int_literal` node and
|
||||
/// ignoring the `string` annotation).
|
||||
pub fn isCountableConstType(table: *const types.TypeTable, ty: TypeId) bool {
|
||||
return switch (ty) {
|
||||
.s8, .s16, .s32, .s64, .u8, .u16, .u32, .u64, .usize, .isize, .f32, .f64 => true,
|
||||
.i8, .i16, .i32, .i64, .u8, .u16, .u32, .u64, .usize, .isize, .f32, .f64 => true,
|
||||
else => if (ty.isBuiltin()) false else switch (table.get(ty)) {
|
||||
.signed, .unsigned => true,
|
||||
else => false,
|
||||
@@ -198,7 +198,7 @@ fn moduleConstIntFramed(consts: *const std.StringHashMap(ModuleConstInfo), table
|
||||
/// laid out via a type alias (`Arr :: [N]T`, stateless) gets a different length
|
||||
/// than the direct form (`a : [N]T`, stateful) — that miscompile class.
|
||||
/// Every const's RHS is folded through the shared `evalConstIntExpr`, so an
|
||||
/// untyped (`N :: 16`) / typed (`N : s64 : 16`) literal, an integral float
|
||||
/// untyped (`N :: 16`) / typed (`N : i64 : 16`) literal, an integral float
|
||||
/// (`N : f64 : 4.0` → 4, via `floatToIntExact`; `4.5` → null), AND an expression
|
||||
/// RHS over other consts (`M :: 2; N :: M + 1` → 3) all resolve identically and
|
||||
/// everywhere a count is accepted. Cyclic consts fold to null (see
|
||||
@@ -212,8 +212,8 @@ pub fn moduleConstInt(consts: *const std.StringHashMap(ModuleConstInfo), table:
|
||||
/// `moduleConstIntFramed` exactly — same `isCountableConstType` gate, same cyclic-
|
||||
/// definition frame — but recovers the value through `evalConstFloatExpr`, so the
|
||||
/// unified float→int narrowing rule resolves a NON-INTEGRAL float-const leaf
|
||||
/// (`y : s64 = F + 0.25`) the same way the int folder resolves an int-const leaf
|
||||
/// (`M :: 2; y : s64 = M + 0.5`). An integral float / integer const folds through
|
||||
/// (`y : i64 = F + 0.25`) the same way the int folder resolves an int-const leaf
|
||||
/// (`M :: 2; y : i64 = M + 0.5`). An integral float / integer const folds through
|
||||
/// the int path inside `evalConstFloatExpr` and never reaches the leaf arm that
|
||||
/// calls this; this surfaces the genuinely non-integral float so `floatToIntExact`
|
||||
/// can reject it.
|
||||
@@ -231,7 +231,7 @@ pub fn moduleConstFloat(consts: *const std.StringHashMap(ModuleConstInfo), table
|
||||
|
||||
/// True iff `name` is a FLOAT-valued module const — judged by VALUE, so it covers
|
||||
/// a typed float const (`K : f64 : 4.0`), an untyped float-EXPRESSION const
|
||||
/// (`ME :: 4.0 + 1.0`, whose placeholder type is `s64`), and a non-integral float
|
||||
/// (`ME :: 4.0 + 1.0`, whose placeholder type is `i64`), and a non-integral float
|
||||
/// const (`F : f64 : 2.5`). SINGLE source for the stateful (`Lowering`) and
|
||||
/// stateless (`type_bridge`) division-arm float checks, so they agree on which
|
||||
/// const-leaf divisions are float.
|
||||
@@ -255,7 +255,7 @@ pub fn moduleConstIsFloatTyped(consts: *const std.StringHashMap(ModuleConstInfo)
|
||||
/// Also the precise "is this a compile-time float-valued initializer" test the
|
||||
/// typed-binding narrowing path (`Lowering.foldComptimeFloatInit`) uses alongside
|
||||
/// `inferExprType`, so an untyped float-EXPRESSION const (`ME :: 4.0 + 1.0`,
|
||||
/// placeholder type `s64`) flowing into an integer binding (`x : s64 = ME / 2`)
|
||||
/// placeholder type `i64`) flowing into an integer binding (`x : i64 = ME / 2`)
|
||||
/// is judged float-valued even though `inferExprType` reads its placeholder type.
|
||||
pub fn isFloatValuedExpr(node: *const Node, ctx: anytype) bool {
|
||||
return switch (node.data) {
|
||||
@@ -323,7 +323,7 @@ pub fn evalConstIntExpr(node: *const Node, ctx: anytype) ?i64 {
|
||||
.identifier => |id| ctx.lookupDimName(id.name),
|
||||
.type_expr => |te| ctx.lookupDimName(te.name),
|
||||
.field_access => |fa| blk: {
|
||||
// A backtick RAW receiver (`` `s64.max ``, `` `f64.epsilon ``) is an
|
||||
// A backtick RAW receiver (`` `i64.max ``, `` `f64.epsilon ``) is an
|
||||
// ordinary field READ on a value whose spelling shadows a builtin
|
||||
// type name, NOT a numeric-limit / pack-arity accessor — so it is
|
||||
// never a compile-time leaf here; its field is a runtime value
|
||||
@@ -596,10 +596,10 @@ pub fn intTypeRange(name: []const u8) ?IntRange {
|
||||
if (eql(u8, name, "u16")) return .{ .min = 0, .max = std.math.maxInt(u16) };
|
||||
if (eql(u8, name, "u32")) return .{ .min = 0, .max = std.math.maxInt(u32) };
|
||||
if (eql(u8, name, "u64") or eql(u8, name, "usize")) return .{ .min = 0, .max = std.math.maxInt(i64) };
|
||||
if (eql(u8, name, "s8")) return .{ .min = std.math.minInt(i8), .max = std.math.maxInt(i8) };
|
||||
if (eql(u8, name, "s16")) return .{ .min = std.math.minInt(i16), .max = std.math.maxInt(i16) };
|
||||
if (eql(u8, name, "s32")) return .{ .min = std.math.minInt(i32), .max = std.math.maxInt(i32) };
|
||||
if (eql(u8, name, "s64") or eql(u8, name, "isize") or eql(u8, name, "int"))
|
||||
if (eql(u8, name, "i8")) return .{ .min = std.math.minInt(i8), .max = std.math.maxInt(i8) };
|
||||
if (eql(u8, name, "i16")) return .{ .min = std.math.minInt(i16), .max = std.math.maxInt(i16) };
|
||||
if (eql(u8, name, "i32")) return .{ .min = std.math.minInt(i32), .max = std.math.maxInt(i32) };
|
||||
if (eql(u8, name, "i64") or eql(u8, name, "isize") or eql(u8, name, "int"))
|
||||
return .{ .min = std.math.minInt(i64), .max = std.math.maxInt(i64) };
|
||||
return null;
|
||||
}
|
||||
@@ -682,7 +682,7 @@ pub const ProgramIndex = struct {
|
||||
protocol_decl_map: std.StringHashMap(ProtocolDeclInfo),
|
||||
/// Protocol name → AST node.
|
||||
protocol_ast_map: std.StringHashMap(*const ast.ProtocolDecl),
|
||||
/// Module-level value constants (e.g. AF_INET :s32: 2).
|
||||
/// Module-level value constants (e.g. AF_INET :i32: 2).
|
||||
module_const_map: std.StringHashMap(ModuleConstInfo),
|
||||
/// UFCS alias name → target function name.
|
||||
ufcs_alias_map: std.StringHashMap([]const u8),
|
||||
|
||||
@@ -52,7 +52,7 @@ test "protocols: getProtocolInfo resolves registered protocol structs only" {
|
||||
try std.testing.expectEqual(@as(usize, 1), info.methods.len);
|
||||
|
||||
// A builtin and an unrelated plain struct are not protocols.
|
||||
try std.testing.expect(pr.getProtocolInfo(.s32) == null);
|
||||
try std.testing.expect(pr.getProtocolInfo(.i32) == null);
|
||||
const plain = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Point"), .fields = &.{} } });
|
||||
try std.testing.expect(pr.getProtocolInfo(plain) == null);
|
||||
|
||||
@@ -162,8 +162,8 @@ test "protocols: registerParamImpl flags a same-file duplicate impl" {
|
||||
const pr = ProtocolResolver{ .l = &l };
|
||||
_ = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("IntCell"), .fields = &.{} } });
|
||||
|
||||
// impl Into(s64) for IntCell { ... } — a parameterised-protocol impl.
|
||||
const args = [_]*Node{typeExpr(alloc, "s64")};
|
||||
// impl Into(i64) for IntCell { ... } — a parameterised-protocol impl.
|
||||
const args = [_]*Node{typeExpr(alloc, "i64")};
|
||||
const conv = mk(alloc, .{ .fn_decl = .{ .name = "convert", .params = &.{}, .return_type = null, .body = emptyBody(alloc) } });
|
||||
const methods = [_]*Node{conv};
|
||||
const ib = ast.ImplBlock{
|
||||
@@ -218,8 +218,8 @@ test "protocols: findVisibleImpls filters by transitive import visibility" {
|
||||
var l = Lowering.init(&module);
|
||||
const pr = ProtocolResolver{ .l = &l };
|
||||
|
||||
const here_entry: Lowering.ParamImplEntry = .{ .methods = &.{}, .source_ty = .s64, .target_args = &.{}, .defining_module = "a.sx", .span = .{ .start = 0, .end = 0 } };
|
||||
const other_entry: Lowering.ParamImplEntry = .{ .methods = &.{}, .source_ty = .s64, .target_args = &.{}, .defining_module = "b.sx", .span = .{ .start = 0, .end = 0 } };
|
||||
const here_entry: Lowering.ParamImplEntry = .{ .methods = &.{}, .source_ty = .i64, .target_args = &.{}, .defining_module = "a.sx", .span = .{ .start = 0, .end = 0 } };
|
||||
const other_entry: Lowering.ParamImplEntry = .{ .methods = &.{}, .source_ty = .i64, .target_args = &.{}, .defining_module = "b.sx", .span = .{ .start = 0, .end = 0 } };
|
||||
const entries = [_]Lowering.ParamImplEntry{ here_entry, other_entry };
|
||||
|
||||
// No source-file context → falls open (all entries visible).
|
||||
@@ -279,6 +279,6 @@ test "protocols: matchPackImpl selects a pack impl whose prefix + return match"
|
||||
try std.testing.expectEqual(TypeId.void, m.src_ret);
|
||||
|
||||
// A non-closure source does not match; an unknown key does not match.
|
||||
try std.testing.expect(pr.matchPackImpl(.s64, pack_key) == null);
|
||||
try std.testing.expect(pr.matchPackImpl(.i64, pack_key) == null);
|
||||
try std.testing.expect(pr.matchPackImpl(src, "Into\x00Nope") == null);
|
||||
}
|
||||
|
||||
@@ -459,7 +459,7 @@ pub const ProtocolResolver = struct {
|
||||
// Concrete-struct source: also register the impl's methods as
|
||||
// `<Source>.<method>` in fn_ast_map so UFCS resolves them (e.g.
|
||||
// `xs[i].get()` on a pack element). For a concrete impl like
|
||||
// `impl Box(s64) for IntCell`, the method is already fully concrete —
|
||||
// `impl Box(i64) for IntCell`, the method is already fully concrete —
|
||||
// nothing to monomorphize, unlike generic/pack sources (which stay
|
||||
// lazy in param_impl_map and are handled below).
|
||||
{
|
||||
|
||||
@@ -94,10 +94,10 @@ test "resolver: collectVisibleAuthors — own author, two distinct flat authors,
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "dup :: () -> s64 { 1 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "dup :: () -> s64 { 2 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "p.sx", .data = "secret :: () -> s64 { 9 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "#import \"a.sx\";\n#import \"b.sx\";\ng :: #import \"p.sx\";\nselfauthored :: () -> s64 { 0 }\nmain :: () -> s32 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "dup :: () -> i64 { 1 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "dup :: () -> i64 { 2 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "p.sx", .data = "secret :: () -> i64 { 9 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "#import \"a.sx\";\n#import \"b.sx\";\ng :: #import \"p.sx\";\nselfauthored :: () -> i64 { 0 }\nmain :: () -> i32 { 0 }\n" });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
@@ -190,8 +190,8 @@ test "resolver: collectNamespaceAuthors — returns target members, walks no gra
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "point.sx", .data = "Point :: struct { x: s64 }\nhelper :: () -> s64 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "g :: #import \"point.sx\";\nmain :: () -> s32 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "point.sx", .data = "Point :: struct { x: i64 }\nhelper :: () -> i64 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "g :: #import \"point.sx\";\nmain :: () -> i32 { 0 }\n" });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
@@ -297,15 +297,15 @@ test "resolver: resolveBare — own_wins / single / ambiguous / not_visible / do
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "dup :: () -> s64 { 1 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "dup :: () -> s64 { 2 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "ns.sx", .data = "secret :: () -> s64 { 9 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "dup :: () -> i64 { 1 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "dup :: () -> i64 { 2 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "ns.sx", .data = "secret :: () -> i64 { 9 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data =
|
||||
\\#import "a.sx";
|
||||
\\#import "b.sx";
|
||||
\\g :: #import "ns.sx";
|
||||
\\selfauthored :: () -> s64 { 0 }
|
||||
\\main :: () -> s32 { 0 }
|
||||
\\selfauthored :: () -> i64 { 0 }
|
||||
\\main :: () -> i32 { 0 }
|
||||
\\
|
||||
});
|
||||
|
||||
@@ -340,8 +340,8 @@ test "resolver: resolveBare — own_wins / single / ambiguous / not_visible / do
|
||||
const df = r.resolveBare("selfauthored", main_path, .bare_type);
|
||||
try std.testing.expectEqual(resolver.Verdict.domain_filtered, df.verdict);
|
||||
|
||||
// domain_filtered with empty set: s64 is a builtin, no user author
|
||||
const builtin = r.resolveBare("s64", main_path, .bare_type);
|
||||
// domain_filtered with empty set: i64 is a builtin, no user author
|
||||
const builtin = r.resolveBare("i64", main_path, .bare_type);
|
||||
try std.testing.expectEqual(resolver.Verdict.domain_filtered, builtin.verdict);
|
||||
try std.testing.expectEqual(@as(usize, 0), builtin.set.distinctCount());
|
||||
}
|
||||
@@ -357,8 +357,8 @@ test "resolver: resolveQualified — single for existing member, domain_filtered
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "point.sx", .data = "Point :: struct { x: s64 }\nhelper :: () -> s64 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "g :: #import \"point.sx\";\nmain :: () -> s32 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "point.sx", .data = "Point :: struct { x: i64 }\nhelper :: () -> i64 { 0 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = "g :: #import \"point.sx\";\nmain :: () -> i32 { 0 }\n" });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const absdir = dirbuf[0..try tmp.dir.realPath(io, &dirbuf)];
|
||||
|
||||
@@ -136,8 +136,8 @@ pub const UnknownTypeChecker = struct {
|
||||
},
|
||||
.fn_decl => |fd| {
|
||||
// A function NAME is a binding site too: a bare reserved-name
|
||||
// `s2 :: (…) {…}` (free fn or struct/impl method) is rejected,
|
||||
// exactly like `s2 := …`. Backtick (`` `s2 :: … ``) and
|
||||
// `i2 :: (…) {…}` (free fn or struct/impl method) is rejected,
|
||||
// exactly like `i2 := …`. Backtick (`` `i2 :: … ``) and
|
||||
// `#import c` foreign fns set `is_raw` and are exempt (0089).
|
||||
self.checkBindingName(fd.name, fd.name_span, fd.is_raw);
|
||||
self.checkParamNames(fd.params);
|
||||
@@ -366,7 +366,7 @@ pub const UnknownTypeChecker = struct {
|
||||
/// (a lambda default), so recurse into it.
|
||||
fn checkParamNames(self: UnknownTypeChecker, params: []const ast.Param) void {
|
||||
for (params) |p| {
|
||||
// A backtick raw param (`` (`s2: T) ``) or a `#import c` foreign
|
||||
// A backtick raw param (`` (`i2: T) ``) or a `#import c` foreign
|
||||
// param is exempt from the reserved-type-name rule —
|
||||
// the exemption is honored inside `checkBindingName` via `p.is_raw`.
|
||||
self.checkBindingName(p.name, p.name_span, p.is_raw);
|
||||
@@ -721,7 +721,7 @@ pub const UnknownTypeChecker = struct {
|
||||
/// struct template's value-param positions come from its declared params; a
|
||||
/// type-RETURNING function (`Make :: ($K: u32, $T: Type) -> Type`) classifies
|
||||
/// each param from its constraint, mirroring `instantiateTypeFunction` — so
|
||||
/// `Make(N, s64)` (N a module const) is not walked as the type name "N".
|
||||
/// `Make(N, i64)` (N a module const) is not walked as the type name "N".
|
||||
fn isValueParamPosition(self: UnknownTypeChecker, base: []const u8, i: usize) bool {
|
||||
if (std.mem.eql(u8, base, "Vector")) return i == 0;
|
||||
if (self.index.struct_template_map.get(base)) |tmpl| {
|
||||
@@ -812,11 +812,11 @@ pub const UnknownTypeChecker = struct {
|
||||
// Only bare identifiers are validated. Inline-spelled compound types
|
||||
// (`[:0]u8`, `mod.Type`, …) carry non-identifier characters — trust them.
|
||||
if (!isIdentLike(name)) return;
|
||||
// A backtick raw reference (`` `s2 ``) is the LITERAL name used as a
|
||||
// A backtick raw reference (`` `i2 ``) is the LITERAL name used as a
|
||||
// type — explicitly NOT the builtin/reserved spelling — so it must
|
||||
// resolve to a `` `s2 ``-declared type, else a normal "unknown type"
|
||||
// resolve to a `` `i2 ``-declared type, else a normal "unknown type"
|
||||
// error. Skip the builtin-name exemption that would otherwise wave a
|
||||
// bare `s2` through.
|
||||
// bare `i2` through.
|
||||
if (!is_raw and isBuiltinTypeName(name)) return;
|
||||
for (in_scope) |tp| {
|
||||
if (!std.mem.eql(u8, tp.name, name)) continue;
|
||||
@@ -891,7 +891,7 @@ pub const UnknownTypeChecker = struct {
|
||||
/// is that classifier (`parser.zig` uses it to choose `.type_expr` over
|
||||
/// `.identifier`), so deferring to it ties the rejection to the parser's set and
|
||||
/// keeps the two from drifting: the named builtins (`bool`, `string`, `void`,
|
||||
/// `f32`, `f64`, `usize`, `isize`, `Any`) and the `[su]N` arbitrary-width ints
|
||||
/// `f32`, `f64`, `usize`, `isize`, `Any`) and the `[iu]N` arbitrary-width ints
|
||||
/// over sx's supported 1–64 range. A bare value name (`s`, `buf`, `index`,
|
||||
/// `self`) is not a type spelling and is left alone.
|
||||
fn isReservedTypeName(name: []const u8) bool {
|
||||
@@ -900,8 +900,8 @@ fn isReservedTypeName(name: []const u8) bool {
|
||||
|
||||
fn isBuiltinTypeName(name: []const u8) bool {
|
||||
if (TypeResolver.resolvePrimitive(name) != null) return true;
|
||||
// Arbitrary-width integers / floats: u1, s7, u128, f16, f80, …
|
||||
if (name.len >= 2 and (name[0] == 'u' or name[0] == 's' or name[0] == 'f')) {
|
||||
// Arbitrary-width integers / floats: u1, i7, u128, f16, f80, …
|
||||
if (name.len >= 2 and (name[0] == 'u' or name[0] == 'i' or name[0] == 'f')) {
|
||||
var all_digits = true;
|
||||
for (name[1..]) |c| {
|
||||
if (!std.ascii.isDigit(c)) {
|
||||
@@ -918,7 +918,7 @@ fn isBuiltinTypeName(name: []const u8) bool {
|
||||
|
||||
/// True when a `const_decl`'s value introduces a TYPE name — a type declaration
|
||||
/// (`struct`/`enum`/`union`/`error`) or a type-expression alias (`Alias :: u32`,
|
||||
/// `Ptr :: *u8`, `Cb :: (s32) -> s32`, …). Only these belong in the declared-
|
||||
/// `Ptr :: *u8`, `Cb :: (i32) -> i32`, …). Only these belong in the declared-
|
||||
/// type-name set; a value const (`NotAType :: 123`) does NOT declare a type and
|
||||
/// must stay subject to the unknown-type check.
|
||||
///
|
||||
|
||||
@@ -30,14 +30,14 @@ test "resolveAstType: pointer type" {
|
||||
|
||||
const inner = try alloc.create(Node);
|
||||
defer alloc.destroy(inner);
|
||||
inner.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s32" } } };
|
||||
inner.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "i32" } } };
|
||||
|
||||
const node = try alloc.create(Node);
|
||||
defer alloc.destroy(node);
|
||||
node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .pointer_type_expr = .{ .pointee_type = inner } } };
|
||||
|
||||
const id = type_bridge.resolveAstType(node, &table, null, null);
|
||||
try std.testing.expectEqual(TypeInfo{ .pointer = .{ .pointee = .s32 } }, table.get(id));
|
||||
try std.testing.expectEqual(TypeInfo{ .pointer = .{ .pointee = .i32 } }, table.get(id));
|
||||
}
|
||||
|
||||
test "resolveAstType: optional slice" {
|
||||
@@ -68,7 +68,7 @@ test "resolveAstType: optional slice" {
|
||||
}
|
||||
}
|
||||
|
||||
test "resolveAstType: null surfaces as .unresolved (no silent s64 default)" {
|
||||
test "resolveAstType: null surfaces as .unresolved (no silent i64 default)" {
|
||||
const alloc = std.testing.allocator;
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
@@ -129,12 +129,12 @@ test "resolveAstType: named-const array dimension resolves to the same length as
|
||||
n_val.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .int_literal = .{ .value = 4 } } };
|
||||
var consts = std.StringHashMap(ModuleConstInfo).init(alloc);
|
||||
defer consts.deinit();
|
||||
try consts.put("N", .{ .value = n_val, .ty = .s64 });
|
||||
try consts.put("N", .{ .value = n_val, .ty = .i64 });
|
||||
|
||||
// `[N]s64` — dimension is the named const `N`, not a literal.
|
||||
// `[N]i64` — dimension is the named const `N`, not a literal.
|
||||
const elem = try alloc.create(Node);
|
||||
defer alloc.destroy(elem);
|
||||
elem.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s64" } } };
|
||||
elem.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "i64" } } };
|
||||
const len_node = try alloc.create(Node);
|
||||
defer alloc.destroy(len_node);
|
||||
len_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = "N" } } };
|
||||
@@ -142,11 +142,11 @@ test "resolveAstType: named-const array dimension resolves to the same length as
|
||||
defer alloc.destroy(arr);
|
||||
arr.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .array_type_expr = .{ .length = len_node, .element_type = elem } } };
|
||||
|
||||
// With the const table threaded, `[N]s64` lays out identically to `[4]s64`.
|
||||
// With the const table threaded, `[N]i64` lays out identically to `[4]i64`.
|
||||
const id = type_bridge.resolveAstType(arr, &table, null, &consts);
|
||||
const info = table.get(id);
|
||||
try std.testing.expect(info == .array);
|
||||
try std.testing.expectEqual(TypeId.s64, info.array.element);
|
||||
try std.testing.expectEqual(TypeId.i64, info.array.element);
|
||||
try std.testing.expectEqual(@as(u32, 4), info.array.length);
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ test "resolveAstType: bare `!` resolves to a shared inferred placeholder set" {
|
||||
try std.testing.expectEqual(ia, ib); // all bare `!` share the placeholder for now
|
||||
}
|
||||
|
||||
test "resolveAstType: `(s32, !Named)` result list is a tuple ending in the error set" {
|
||||
test "resolveAstType: `(i32, !Named)` result list is a tuple ending in the error set" {
|
||||
// resolveTupleType allocates its field slice via `table.alloc` (the real
|
||||
// compiler backs the table with an arena), so use one here.
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
@@ -223,7 +223,7 @@ test "resolveAstType: `(s32, !Named)` result list is a tuple ending in the error
|
||||
|
||||
const val_ty = try alloc.create(Node);
|
||||
defer alloc.destroy(val_ty);
|
||||
val_ty.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s32" } } };
|
||||
val_ty.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "i32" } } };
|
||||
const err_ty = try alloc.create(Node);
|
||||
defer alloc.destroy(err_ty);
|
||||
err_ty.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .error_type_expr = .{ .name = "IoErr" } } };
|
||||
@@ -236,6 +236,6 @@ test "resolveAstType: `(s32, !Named)` result list is a tuple ending in the error
|
||||
const info = table.get(id);
|
||||
try std.testing.expect(info == .tuple);
|
||||
try std.testing.expectEqual(@as(usize, 2), info.tuple.fields.len);
|
||||
try std.testing.expectEqual(TypeId.s32, info.tuple.fields[0]);
|
||||
try std.testing.expectEqual(TypeId.i32, info.tuple.fields[0]);
|
||||
try std.testing.expectEqual(set, info.tuple.fields[1]); // error channel = last slot
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ const StatelessInner = struct {
|
||||
return resolveAstType(node, self.table, self.alias_map, self.consts);
|
||||
}
|
||||
/// Fixed-array dimension at registration time: a literal `[16]T`, a named
|
||||
/// module-global const `N :: 16; [N]T` (typed `N : s64 : 16` too), or a
|
||||
/// module-global const `N :: 16; [N]T` (typed `N : i64 : 16` too), or a
|
||||
/// constant-foldable expression over those (`[M + 1]`, `[(M + 1) * 2]`).
|
||||
/// Folds and narrows through the shared `program_index.foldDimU32` (min 0) —
|
||||
/// the SAME range-checked fold-to-u32 the stateful body-lowering path uses —
|
||||
@@ -133,7 +133,7 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap
|
||||
// A null node means a caller reached type resolution without a type node.
|
||||
// Every current caller either passes a non-optional node or handles the
|
||||
// "no type" case itself (returning `.void`), so this is a caller bug — and
|
||||
// `.s64` here would silently fabricate an 8-byte int. Surface it via the
|
||||
// `.i64` here would silently fabricate an 8-byte int. Surface it via the
|
||||
// `.unresolved` sentinel (trips the sizeOf/toLLVMType panic at codegen).
|
||||
const n = node orelse return .unresolved;
|
||||
const si = StatelessInner{ .table = table, .alias_map = alias_map, .consts = consts };
|
||||
@@ -167,7 +167,7 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap
|
||||
// to that state. The pack-aware caller (lowering's
|
||||
// `resolveTypeWithBindings`) handles this case directly *before*
|
||||
// delegating here, so reaching this bare path means the binding
|
||||
// was missing. `.s64` would silently fabricate an 8-byte int;
|
||||
// was missing. `.i64` would silently fabricate an 8-byte int;
|
||||
// return `.unresolved` so it surfaces (trips the sizeOf/toLLVMType
|
||||
// panic at codegen).
|
||||
std.debug.print("type_bridge: pack-index type expression encountered outside a pack-aware context — returning .unresolved\n", .{});
|
||||
@@ -177,7 +177,7 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap
|
||||
.parameterized_type_expr => |pt| resolveParameterizedType(&pt, table, alias_map, consts),
|
||||
// An unannotated param. Its type must be resolved from context
|
||||
// (contextual closure typing, generic binding, or pack substitution)
|
||||
// *before* reaching here; if it doesn't, returning a plausible `.s64`
|
||||
// *before* reaching here; if it doesn't, returning a plausible `.i64`
|
||||
// silently fabricates an 8-byte int (the classic silent-default trap).
|
||||
// Return the dedicated `.unresolved` sentinel — never a legitimate
|
||||
// type — so the omission surfaces; the lowering-side `resolveParamType`
|
||||
@@ -191,7 +191,7 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap
|
||||
.error_type_expr => |ete| resolveErrorType(&ete, table, alias_map),
|
||||
else => {
|
||||
// A non-type AST node reached type resolution — a caller bug.
|
||||
// Returning a plausible `.s64` would silently fabricate an 8-byte
|
||||
// Returning a plausible `.i64` would silently fabricate an 8-byte
|
||||
// int; return the `.unresolved` sentinel so it surfaces (and trips
|
||||
// the sizeOf/toLLVMType panic if it ever reaches codegen).
|
||||
std.debug.print("type_bridge: unhandled node type {s} in type position — returning .unresolved\n", .{@tagName(n.data)});
|
||||
@@ -258,8 +258,8 @@ fn resolveTupleSpreadShape(tt: *const ast.TupleTypeExpr, table: *TypeTable, alia
|
||||
|
||||
// Treat a tuple value literal as the corresponding tuple TYPE — valid only when
|
||||
// every element is itself a type expression. A non-type element (e.g. the `1`
|
||||
// in `(s32, 1)`) means this literal is NOT a type: refuse to fabricate a tuple
|
||||
// and return the `.unresolved` sentinel (never `.s64`, which would silently lie
|
||||
// in `(i32, 1)`) means this literal is NOT a type: refuse to fabricate a tuple
|
||||
// and return the `.unresolved` sentinel (never `.i64`, which would silently lie
|
||||
// about the size). type_bridge is stateless and has no diagnostics;
|
||||
// the user-facing diagnostic is emitted by the stateful caller
|
||||
// (`Lowering.resolveTupleLiteralTypeArg`), which validates before delegating
|
||||
@@ -447,7 +447,7 @@ pub fn buildEnumInfo(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: Alia
|
||||
return .{ .tagged_union = .{
|
||||
.name = name_id,
|
||||
.fields = fields.items,
|
||||
.tag_type = tag_type orelse .s64, // enum unions are always tagged (default i64)
|
||||
.tag_type = tag_type orelse .i64, // enum unions are always tagged (default i64)
|
||||
.backing_type = backing_type,
|
||||
.explicit_tag_values = explicit_tag_vals,
|
||||
} };
|
||||
|
||||
@@ -32,7 +32,7 @@ const PrimInner = struct {
|
||||
};
|
||||
|
||||
test "TypeResolver.resolvePrimitive maps builtin keywords, null otherwise" {
|
||||
try std.testing.expectEqual(@as(?TypeId, .s64), TypeResolver.resolvePrimitive("s64"));
|
||||
try std.testing.expectEqual(@as(?TypeId, .i64), TypeResolver.resolvePrimitive("i64"));
|
||||
try std.testing.expectEqual(@as(?TypeId, .bool), TypeResolver.resolvePrimitive("bool"));
|
||||
try std.testing.expectEqual(@as(?TypeId, .f64), TypeResolver.resolvePrimitive("f64"));
|
||||
try std.testing.expectEqual(@as(?TypeId, .void), TypeResolver.resolvePrimitive("void"));
|
||||
@@ -56,14 +56,14 @@ test "TypeResolver.resolveCompound builds structural compound types" {
|
||||
var table = TypeTable.init(alloc);
|
||||
const inner = PrimInner{};
|
||||
|
||||
var s64n = typeExpr("s64");
|
||||
var s64n = typeExpr("i64");
|
||||
var u8n = typeExpr("u8");
|
||||
var f32n = typeExpr("f32");
|
||||
var booln = typeExpr("bool");
|
||||
var s32n = typeExpr("s32");
|
||||
var s32n = typeExpr("i32");
|
||||
|
||||
var ptr = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .pointer_type_expr = .{ .pointee_type = &s64n } } };
|
||||
try std.testing.expectEqual(@as(?TypeId, table.ptrTo(.s64)), TypeResolver.resolveCompound(&table, &ptr, inner));
|
||||
try std.testing.expectEqual(@as(?TypeId, table.ptrTo(.i64)), TypeResolver.resolveCompound(&table, &ptr, inner));
|
||||
|
||||
var mptr = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .many_pointer_type_expr = .{ .element_type = &u8n } } };
|
||||
try std.testing.expectEqual(@as(?TypeId, table.manyPtrTo(.u8)), TypeResolver.resolveCompound(&table, &mptr, inner));
|
||||
@@ -76,22 +76,22 @@ test "TypeResolver.resolveCompound builds structural compound types" {
|
||||
|
||||
var len = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .int_literal = .{ .value = 3 } } };
|
||||
var arr = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .array_type_expr = .{ .length = &len, .element_type = &s32n } } };
|
||||
try std.testing.expectEqual(@as(?TypeId, table.arrayOf(.s32, 3)), TypeResolver.resolveCompound(&table, &arr, inner));
|
||||
try std.testing.expectEqual(@as(?TypeId, table.arrayOf(.i32, 3)), TypeResolver.resolveCompound(&table, &arr, inner));
|
||||
|
||||
// Function type `(s64) -> bool` — resolveCompound owns it (A2.3b).
|
||||
// Function type `(i64) -> bool` — resolveCompound owns it (A2.3b).
|
||||
const fparams = [_]*Node{&s64n};
|
||||
var fnode = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .function_type_expr = .{ .param_types = &fparams, .return_type = &booln } } };
|
||||
try std.testing.expectEqual(@as(?TypeId, table.functionTypeCC(&[_]TypeId{.s64}, .bool, .default)), TypeResolver.resolveCompound(&table, &fnode, inner));
|
||||
try std.testing.expectEqual(@as(?TypeId, table.functionTypeCC(&[_]TypeId{.i64}, .bool, .default)), TypeResolver.resolveCompound(&table, &fnode, inner));
|
||||
|
||||
// Plain closure `Closure(s64) -> bool` (no pack) — owned here.
|
||||
// Plain closure `Closure(i64) -> bool` (no pack) — owned here.
|
||||
const cparams = [_]*Node{&s64n};
|
||||
var cnode = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .closure_type_expr = .{ .param_types = &cparams, .return_type = &booln } } };
|
||||
try std.testing.expectEqual(@as(?TypeId, table.closureType(&[_]TypeId{.s64}, .bool)), TypeResolver.resolveCompound(&table, &cnode, inner));
|
||||
try std.testing.expectEqual(@as(?TypeId, table.closureType(&[_]TypeId{.i64}, .bool)), TypeResolver.resolveCompound(&table, &cnode, inner));
|
||||
|
||||
// Plain positional tuple `(s64, bool)` — owned here.
|
||||
// Plain positional tuple `(i64, bool)` — owned here.
|
||||
const tfields = [_]*Node{ &s64n, &booln };
|
||||
var tnode = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .tuple_type_expr = .{ .field_types = &tfields, .field_names = null } } };
|
||||
const want_tuple = table.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .s64, .bool }, .names = null } });
|
||||
const want_tuple = table.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .i64, .bool }, .names = null } });
|
||||
try std.testing.expectEqual(@as(?TypeId, want_tuple), TypeResolver.resolveCompound(&table, &tnode, inner));
|
||||
|
||||
// Pack-shaped `Closure(..p)` → null (needs caller pack state → PackResolver).
|
||||
@@ -121,11 +121,11 @@ test "TypeResolver.resolveBinding reads ResolveEnv type bindings ($T)" {
|
||||
const alloc = std.testing.allocator;
|
||||
var tb = std.StringHashMap(TypeId).init(alloc);
|
||||
defer tb.deinit();
|
||||
try tb.put("T", .s64);
|
||||
try tb.put("T", .i64);
|
||||
const env = ResolveEnv{ .type_bindings = &tb };
|
||||
|
||||
var bound = typeExpr("T");
|
||||
try std.testing.expectEqual(@as(?TypeId, .s64), TypeResolver.resolveBinding(&bound, env));
|
||||
try std.testing.expectEqual(@as(?TypeId, .i64), TypeResolver.resolveBinding(&bound, env));
|
||||
// Unbound name → null (caller continues with primitive / alias / struct).
|
||||
var unbound = typeExpr("U");
|
||||
try std.testing.expect(TypeResolver.resolveBinding(&unbound, env) == null);
|
||||
@@ -140,22 +140,22 @@ test "TypeResolver.resolveName resolves aliases via ProgramIndex (not the TypeTa
|
||||
var index = ProgramIndex.init(alloc);
|
||||
defer index.deinit();
|
||||
try index.type_alias_map.put("ShaderHandle", .u32); // alias → primitive
|
||||
const ptr_s64 = table.ptrTo(.s64);
|
||||
try index.type_alias_map.put("NodeRef", ptr_s64); // alias → pointer
|
||||
const ptr_i64 = table.ptrTo(.i64);
|
||||
try index.type_alias_map.put("NodeRef", ptr_i64); // alias → pointer
|
||||
const tr = TypeResolver{ .alloc = alloc, .types = &table, .diagnostics = null, .index = &index };
|
||||
|
||||
try std.testing.expectEqual(@as(TypeId, .u32), tr.resolveName("ShaderHandle", false));
|
||||
try std.testing.expectEqual(ptr_s64, tr.resolveName("NodeRef", false));
|
||||
try std.testing.expectEqual(ptr_i64, tr.resolveName("NodeRef", false));
|
||||
// Primitive is checked before alias.
|
||||
try std.testing.expectEqual(@as(TypeId, .s64), tr.resolveName("s64", false));
|
||||
try std.testing.expectEqual(@as(TypeId, .i64), tr.resolveName("i64", false));
|
||||
}
|
||||
|
||||
test "TypeResolver.resolveNamed: width-int, string-prefix, unknown→stub" {
|
||||
const alloc = std.testing.allocator;
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
try std.testing.expectEqual(table.intern(.{ .signed = 7 }), TypeResolver.resolveNamed("s7", &table, null, false));
|
||||
try std.testing.expectEqual(table.ptrTo(.s64), TypeResolver.resolveNamed("*s64", &table, null, false));
|
||||
try std.testing.expectEqual(table.intern(.{ .signed = 7 }), TypeResolver.resolveNamed("i7", &table, null, false));
|
||||
try std.testing.expectEqual(table.ptrTo(.i64), TypeResolver.resolveNamed("*i64", &table, null, false));
|
||||
// Unknown name, no alias map → empty-struct stub (preserved behavior;
|
||||
// never `.unresolved`, which is reserved for failed *generic* resolution).
|
||||
try std.testing.expect(TypeResolver.resolveNamed("Unknown", &table, null, false) != .unresolved);
|
||||
@@ -165,23 +165,23 @@ test "TypeResolver.resolveNamed: skip_builtin resolves a raw reserved-name type,
|
||||
const alloc = std.testing.allocator;
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
// A registered user type named "s2" (a reserved int spelling).
|
||||
const name_id = table.internString("s2");
|
||||
const user_s2 = table.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } });
|
||||
// A registered user type named "i2" (a reserved int spelling).
|
||||
const name_id = table.internString("i2");
|
||||
const user_i2 = table.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } });
|
||||
// Bare lookup → the builtin 2-bit signed int; raw lookup → the user type.
|
||||
try std.testing.expectEqual(table.intern(.{ .signed = 2 }), TypeResolver.resolveNamed("s2", &table, null, false));
|
||||
try std.testing.expectEqual(user_s2, TypeResolver.resolveNamed("s2", &table, null, true));
|
||||
try std.testing.expectEqual(table.intern(.{ .signed = 2 }), TypeResolver.resolveNamed("i2", &table, null, false));
|
||||
try std.testing.expectEqual(user_i2, TypeResolver.resolveNamed("i2", &table, null, true));
|
||||
}
|
||||
|
||||
test "TypeResolver.parseWidthInt: every width 1..64, both signs; rejects out-of-range / non-int" {
|
||||
// The single width parser — covers the named primitives (s8/u64/…) too.
|
||||
try std.testing.expectEqual(@as(?TypeResolver.WidthInt, .{ .width = 1, .signed = true }), TypeResolver.parseWidthInt("s1"));
|
||||
try std.testing.expectEqual(@as(?TypeResolver.WidthInt, .{ .width = 3, .signed = true }), TypeResolver.parseWidthInt("s3"));
|
||||
try std.testing.expectEqual(@as(?TypeResolver.WidthInt, .{ .width = 64, .signed = true }), TypeResolver.parseWidthInt("s64"));
|
||||
// The single width parser — covers the named primitives (i8/u64/…) too.
|
||||
try std.testing.expectEqual(@as(?TypeResolver.WidthInt, .{ .width = 1, .signed = true }), TypeResolver.parseWidthInt("i1"));
|
||||
try std.testing.expectEqual(@as(?TypeResolver.WidthInt, .{ .width = 3, .signed = true }), TypeResolver.parseWidthInt("i3"));
|
||||
try std.testing.expectEqual(@as(?TypeResolver.WidthInt, .{ .width = 64, .signed = true }), TypeResolver.parseWidthInt("i64"));
|
||||
try std.testing.expectEqual(@as(?TypeResolver.WidthInt, .{ .width = 1, .signed = false }), TypeResolver.parseWidthInt("u1"));
|
||||
try std.testing.expectEqual(@as(?TypeResolver.WidthInt, .{ .width = 64, .signed = false }), TypeResolver.parseWidthInt("u64"));
|
||||
// Width 0 and >64, and non-`s`/`u` names, are not width-ints.
|
||||
try std.testing.expect(TypeResolver.parseWidthInt("s0") == null);
|
||||
try std.testing.expect(TypeResolver.parseWidthInt("i0") == null);
|
||||
try std.testing.expect(TypeResolver.parseWidthInt("u65") == null);
|
||||
try std.testing.expect(TypeResolver.parseWidthInt("usize") == null);
|
||||
try std.testing.expect(TypeResolver.parseWidthInt("f32") == null);
|
||||
@@ -207,33 +207,33 @@ test "TypeResolver.integerLimitFor: pinned min/max across widths and extremes" {
|
||||
}
|
||||
};
|
||||
// Sub-byte widths (arbitrary bit-width arithmetic, not a per-name table).
|
||||
try std.testing.expectEqual(@as(i64, -1), L.v("s1", "min"));
|
||||
try std.testing.expectEqual(@as(i64, 0), L.v("s1", "max"));
|
||||
try std.testing.expectEqual(@as(i64, -2), L.v("s2", "min"));
|
||||
try std.testing.expectEqual(@as(i64, 1), L.v("s2", "max"));
|
||||
try std.testing.expectEqual(@as(i64, 3), L.v("s3", "max"));
|
||||
try std.testing.expectEqual(@as(i64, -1), L.v("i1", "min"));
|
||||
try std.testing.expectEqual(@as(i64, 0), L.v("i1", "max"));
|
||||
try std.testing.expectEqual(@as(i64, -2), L.v("i2", "min"));
|
||||
try std.testing.expectEqual(@as(i64, 1), L.v("i2", "max"));
|
||||
try std.testing.expectEqual(@as(i64, 3), L.v("i3", "max"));
|
||||
try std.testing.expectEqual(@as(i64, 0), L.v("u1", "min"));
|
||||
try std.testing.expectEqual(@as(i64, 1), L.v("u1", "max"));
|
||||
try std.testing.expectEqual(@as(i64, 3), L.v("u2", "max"));
|
||||
// Byte / word.
|
||||
try std.testing.expectEqual(@as(i64, -128), L.v("s8", "min"));
|
||||
try std.testing.expectEqual(@as(i64, 127), L.v("s8", "max"));
|
||||
try std.testing.expectEqual(@as(i64, -128), L.v("i8", "min"));
|
||||
try std.testing.expectEqual(@as(i64, 127), L.v("i8", "max"));
|
||||
try std.testing.expectEqual(@as(i64, 255), L.v("u8", "max"));
|
||||
try std.testing.expectEqual(@as(i64, -2147483648), L.v("s32", "min"));
|
||||
try std.testing.expectEqual(@as(i64, 2147483647), L.v("s32", "max"));
|
||||
// s64 extremes = i64 extremes.
|
||||
try std.testing.expectEqual(std.math.minInt(i64), L.v("s64", "min"));
|
||||
try std.testing.expectEqual(std.math.maxInt(i64), L.v("s64", "max"));
|
||||
try std.testing.expectEqual(@as(i64, -2147483648), L.v("i32", "min"));
|
||||
try std.testing.expectEqual(@as(i64, 2147483647), L.v("i32", "max"));
|
||||
// i64 extremes = i64 extremes.
|
||||
try std.testing.expectEqual(std.math.minInt(i64), L.v("i64", "min"));
|
||||
try std.testing.expectEqual(std.math.maxInt(i64), L.v("i64", "max"));
|
||||
// u63.max fits i64; u64.max is all-ones (= -1 as i64, maxInt(u64) as u64).
|
||||
try std.testing.expectEqual(std.math.maxInt(i64), L.v("u63", "max"));
|
||||
try std.testing.expectEqual(@as(i64, -1), L.v("u64", "max"));
|
||||
try std.testing.expectEqual(std.math.maxInt(u64), @as(u64, @bitCast(L.v("u64", "max"))));
|
||||
try std.testing.expectEqual(@as(i64, 0), L.v("u64", "min"));
|
||||
// usize/isize track u64/s64 on the host.
|
||||
// usize/isize track u64/i64 on the host.
|
||||
try std.testing.expectEqual(L.v("u64", "max"), L.v("usize", "max"));
|
||||
try std.testing.expectEqual(@as(i64, 0), L.v("usize", "min"));
|
||||
try std.testing.expectEqual(L.v("s64", "min"), L.v("isize", "min"));
|
||||
try std.testing.expectEqual(L.v("s64", "max"), L.v("isize", "max"));
|
||||
try std.testing.expectEqual(L.v("i64", "min"), L.v("isize", "min"));
|
||||
try std.testing.expectEqual(L.v("i64", "max"), L.v("isize", "max"));
|
||||
}
|
||||
|
||||
test "TypeResolver.integerLimitFor: null for non-integer receivers and non-limit fields" {
|
||||
@@ -243,7 +243,7 @@ test "TypeResolver.integerLimitFor: null for non-integer receivers and non-limit
|
||||
try std.testing.expect(TypeResolver.integerLimitFor("void", "min") == null);
|
||||
try std.testing.expect(TypeResolver.integerLimitFor("MyStruct", "min") == null);
|
||||
// A builtin int with a non-limit field is not a fold here.
|
||||
try std.testing.expect(TypeResolver.integerLimitFor("s64", "len") == null);
|
||||
try std.testing.expect(TypeResolver.integerLimitFor("i64", "len") == null);
|
||||
try std.testing.expect(TypeResolver.integerLimitFor("u8", "epsilon") == null);
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ test "TypeResolver.floatLimitFor: pinned f32 bit patterns (widened value narrows
|
||||
|
||||
test "TypeResolver.floatLimitFor: null for non-float receivers and non-limit fields" {
|
||||
// Integer / non-numeric / user names are not float-limit folds.
|
||||
try std.testing.expect(TypeResolver.floatLimitFor("s32", "epsilon") == null);
|
||||
try std.testing.expect(TypeResolver.floatLimitFor("i32", "epsilon") == null);
|
||||
try std.testing.expect(TypeResolver.floatLimitFor("u64", "max") == null);
|
||||
try std.testing.expect(TypeResolver.floatLimitFor("usize", "min") == null);
|
||||
try std.testing.expect(TypeResolver.floatLimitFor("bool", "nan") == null);
|
||||
|
||||
@@ -43,10 +43,10 @@ pub const TypeResolver = struct {
|
||||
/// Namespaced (no `self`) — primitive resolution is stateless.
|
||||
pub fn resolvePrimitive(name: []const u8) ?TypeId {
|
||||
if (name.len == 0) return null;
|
||||
if (std.mem.eql(u8, name, "s64")) return .s64;
|
||||
if (std.mem.eql(u8, name, "s32")) return .s32;
|
||||
if (std.mem.eql(u8, name, "s16")) return .s16;
|
||||
if (std.mem.eql(u8, name, "s8")) return .s8;
|
||||
if (std.mem.eql(u8, name, "i64")) return .i64;
|
||||
if (std.mem.eql(u8, name, "i32")) return .i32;
|
||||
if (std.mem.eql(u8, name, "i16")) return .i16;
|
||||
if (std.mem.eql(u8, name, "i8")) return .i8;
|
||||
if (std.mem.eql(u8, name, "u64")) return .u64;
|
||||
if (std.mem.eql(u8, name, "u32")) return .u32;
|
||||
if (std.mem.eql(u8, name, "u16")) return .u16;
|
||||
@@ -67,8 +67,8 @@ pub const TypeResolver = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// An arbitrary-bit-width integer type NAME (`s1`–`s64`, `u1`–`u64`, which
|
||||
/// also subsumes `s8`/`u8`/…/`s64`/`u64`): its width + signedness, else
|
||||
/// An arbitrary-bit-width integer type NAME (`i1`–`i64`, `u1`–`u64`, which
|
||||
/// also subsumes `i8`/`u8`/…/`i64`/`u64`): its width + signedness, else
|
||||
/// null. THE single width parser — `resolveBuiltinName` (to intern the
|
||||
/// `TypeId`) and the numeric-limit accessors (`.min`/`.max`, via
|
||||
/// `integerWidthSign`) both classify through here, so the recognized width
|
||||
@@ -76,10 +76,10 @@ pub const TypeResolver = struct {
|
||||
pub const WidthInt = struct { width: u8, signed: bool };
|
||||
pub fn parseWidthInt(name: []const u8) ?WidthInt {
|
||||
if (name.len < 2) return null;
|
||||
if (name[0] != 's' and name[0] != 'u') return null;
|
||||
if (name[0] != 'i' and name[0] != 'u') return null;
|
||||
const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
|
||||
if (width < 1 or width > 64) return null;
|
||||
return .{ .width = width, .signed = name[0] == 's' };
|
||||
return .{ .width = width, .signed = name[0] == 'i' };
|
||||
}
|
||||
|
||||
/// A bare name → its builtin `TypeId` (primitive keyword OR arbitrary-width
|
||||
@@ -96,7 +96,7 @@ pub const TypeResolver = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Width + signedness of a builtin INTEGER type NAME: the `s`/`u` widths via
|
||||
/// Width + signedness of a builtin INTEGER type NAME: the `i`/`u` widths via
|
||||
/// `parseWidthInt`, plus `usize`/`isize` (target-width = 64 on the host).
|
||||
/// null for a non-integer name (floats, `bool`, `string`, a user type, …) —
|
||||
/// so the `.min`/`.max` fold fires for integers only.
|
||||
@@ -280,7 +280,7 @@ pub const TypeResolver = struct {
|
||||
}
|
||||
|
||||
/// Resolve a bare type NAME to a `TypeId`: primitive → arbitrary-width int
|
||||
/// (`s1`–`u64`) → string-form pointer/slice/optional prefixes → already-
|
||||
/// (`i1`–`u64`) → string-form pointer/slice/optional prefixes → already-
|
||||
/// registered named type → alias (`alias_map`) → fresh empty-struct stub.
|
||||
/// `alias_map` is the single-source alias table (owned by `ProgramIndex`);
|
||||
/// callers pass it explicitly — Lowering via the index (`resolveName`),
|
||||
@@ -288,15 +288,15 @@ pub const TypeResolver = struct {
|
||||
/// stub fall-through preserves long-standing behavior for as-yet-
|
||||
/// unregistered names.
|
||||
///
|
||||
/// `skip_builtin` is the backtick raw-identifier escape (`` `s2 `` in type
|
||||
/// `skip_builtin` is the backtick raw-identifier escape (`` `i2 `` in type
|
||||
/// position): a raw reference is the LITERAL name used as a
|
||||
/// type, so it bypasses the builtin/reserved classifier and resolves only
|
||||
/// through registered-type → alias → stub. A bare `s2` keeps the default
|
||||
/// through registered-type → alias → stub. A bare `i2` keeps the default
|
||||
/// (`false`) and resolves to the builtin int type. The string-prefix
|
||||
/// recursion always passes `false`: the inner names (`*T`/`?T`) are bare,
|
||||
/// never raw.
|
||||
pub fn resolveNamed(name: []const u8, table: *TypeTable, alias_map: ?*const std.StringHashMap(TypeId), skip_builtin: bool) TypeId {
|
||||
// Builtin primitive keyword or arbitrary-width integer (`s1`-`s64`,
|
||||
// Builtin primitive keyword or arbitrary-width integer (`i1`-`i64`,
|
||||
// `u1`-`u64`) — the single builtin classifier, also reused by the
|
||||
// numeric-limit accessor intercept.
|
||||
if (!skip_builtin) {
|
||||
|
||||
@@ -14,7 +14,7 @@ test "builtin types pre-populated" {
|
||||
// Verify builtin slots
|
||||
try std.testing.expectEqual(TypeInfo.void, table.get(.void));
|
||||
try std.testing.expectEqual(TypeInfo.bool, table.get(.bool));
|
||||
try std.testing.expectEqual(TypeInfo{ .signed = 32 }, table.get(.s32));
|
||||
try std.testing.expectEqual(TypeInfo{ .signed = 32 }, table.get(.i32));
|
||||
try std.testing.expectEqual(TypeInfo{ .unsigned = 8 }, table.get(.u8));
|
||||
try std.testing.expectEqual(TypeInfo.f64, table.get(.f64));
|
||||
try std.testing.expectEqual(TypeInfo.string, table.get(.string));
|
||||
@@ -26,8 +26,8 @@ test "intern deduplicates structural types" {
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
|
||||
const ptr1 = table.ptrTo(.s32);
|
||||
const ptr2 = table.ptrTo(.s32);
|
||||
const ptr1 = table.ptrTo(.i32);
|
||||
const ptr2 = table.ptrTo(.i32);
|
||||
try std.testing.expectEqual(ptr1, ptr2);
|
||||
|
||||
const ptr3 = table.ptrTo(.f64);
|
||||
@@ -39,8 +39,8 @@ test "slice and array interning" {
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
|
||||
const slice1 = table.sliceOf(.s32);
|
||||
const slice2 = table.sliceOf(.s32);
|
||||
const slice1 = table.sliceOf(.i32);
|
||||
const slice2 = table.sliceOf(.i32);
|
||||
try std.testing.expectEqual(slice1, slice2);
|
||||
|
||||
const arr1 = table.arrayOf(.u8, 10);
|
||||
@@ -55,8 +55,8 @@ test "optional interning" {
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
|
||||
const opt1 = table.optionalOf(.s32);
|
||||
const opt2 = table.optionalOf(.s32);
|
||||
const opt1 = table.optionalOf(.i32);
|
||||
const opt2 = table.optionalOf(.i32);
|
||||
try std.testing.expectEqual(opt1, opt2);
|
||||
|
||||
const opt3 = table.optionalOf(.f64);
|
||||
@@ -68,9 +68,9 @@ test "function type interning" {
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
|
||||
const params = &[_]TypeId{ .s32, .s32 };
|
||||
const fn1 = table.functionType(params, .s64);
|
||||
const fn2 = table.functionType(params, .s64);
|
||||
const params = &[_]TypeId{ .i32, .i32 };
|
||||
const fn1 = table.functionType(params, .i64);
|
||||
const fn2 = table.functionType(params, .i64);
|
||||
try std.testing.expectEqual(fn1, fn2);
|
||||
|
||||
const fn3 = table.functionType(params, .f64);
|
||||
@@ -99,14 +99,14 @@ test "sizeOf builtins" {
|
||||
|
||||
try std.testing.expectEqual(@as(u32, 0), table.sizeOf(.void));
|
||||
try std.testing.expectEqual(@as(u32, 1), table.sizeOf(.bool));
|
||||
try std.testing.expectEqual(@as(u32, 4), table.sizeOf(.s32));
|
||||
try std.testing.expectEqual(@as(u32, 8), table.sizeOf(.s64));
|
||||
try std.testing.expectEqual(@as(u32, 4), table.sizeOf(.i32));
|
||||
try std.testing.expectEqual(@as(u32, 8), table.sizeOf(.i64));
|
||||
try std.testing.expectEqual(@as(u32, 1), table.sizeOf(.u8));
|
||||
try std.testing.expectEqual(@as(u32, 4), table.sizeOf(.f32));
|
||||
try std.testing.expectEqual(@as(u32, 8), table.sizeOf(.f64));
|
||||
try std.testing.expectEqual(@as(u32, 16), table.sizeOf(.string));
|
||||
try std.testing.expectEqual(@as(u32, 8), table.sizeOf(table.ptrTo(.s32)));
|
||||
try std.testing.expectEqual(@as(u32, 16), table.sizeOf(table.sliceOf(.s32)));
|
||||
try std.testing.expectEqual(@as(u32, 8), table.sizeOf(table.ptrTo(.i32)));
|
||||
try std.testing.expectEqual(@as(u32, 16), table.sizeOf(table.sliceOf(.i32)));
|
||||
}
|
||||
|
||||
test "typeName for builtins" {
|
||||
@@ -114,7 +114,7 @@ test "typeName for builtins" {
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
|
||||
try std.testing.expectEqualStrings("s32", table.typeName(.s32));
|
||||
try std.testing.expectEqualStrings("i32", table.typeName(.i32));
|
||||
try std.testing.expectEqualStrings("bool", table.typeName(.bool));
|
||||
try std.testing.expectEqualStrings("string", table.typeName(.string));
|
||||
try std.testing.expectEqualStrings("void", table.typeName(.void));
|
||||
@@ -128,7 +128,7 @@ test "pack type: construct, element access, intern dedup (N=3)" {
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
|
||||
const elems = &[_]TypeId{ .bool, .s32, .string };
|
||||
const elems = &[_]TypeId{ .bool, .i32, .string };
|
||||
const p1 = table.packType(elems);
|
||||
const p2 = table.packType(elems);
|
||||
try std.testing.expectEqual(p1, p2); // structural dedup
|
||||
@@ -137,7 +137,7 @@ test "pack type: construct, element access, intern dedup (N=3)" {
|
||||
try std.testing.expect(info == .pack);
|
||||
try std.testing.expectEqual(@as(usize, 3), info.pack.elements.len);
|
||||
try std.testing.expectEqual(TypeId.bool, info.pack.elements[0]);
|
||||
try std.testing.expectEqual(TypeId.s32, info.pack.elements[1]);
|
||||
try std.testing.expectEqual(TypeId.i32, info.pack.elements[1]);
|
||||
try std.testing.expectEqual(TypeId.string, info.pack.elements[2]);
|
||||
}
|
||||
|
||||
@@ -170,14 +170,14 @@ test "pack type: distinct element lists are distinct types" {
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
|
||||
const a = table.packType(&[_]TypeId{ .bool, .s32 });
|
||||
const b = table.packType(&[_]TypeId{ .s32, .bool }); // order matters
|
||||
const a = table.packType(&[_]TypeId{ .bool, .i32 });
|
||||
const b = table.packType(&[_]TypeId{ .i32, .bool }); // order matters
|
||||
const c = table.packType(&[_]TypeId{.bool}); // arity matters
|
||||
try std.testing.expect(a != b);
|
||||
try std.testing.expect(a != c);
|
||||
try std.testing.expect(b != c);
|
||||
// A pack is distinct from the tuple of the same elements.
|
||||
const tup = table.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .bool, .s32 }, .names = null } });
|
||||
const tup = table.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .bool, .i32 }, .names = null } });
|
||||
try std.testing.expect(a != tup);
|
||||
}
|
||||
|
||||
@@ -189,8 +189,8 @@ test "pack type: formatTypeName" {
|
||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
||||
defer arena.deinit();
|
||||
|
||||
const p = table.packType(&[_]TypeId{ .bool, .s32, .string });
|
||||
try std.testing.expectEqualStrings("pack(bool, s32, string)", table.formatTypeName(arena.allocator(), p));
|
||||
const p = table.packType(&[_]TypeId{ .bool, .i32, .string });
|
||||
try std.testing.expectEqualStrings("pack(bool, i32, string)", table.formatTypeName(arena.allocator(), p));
|
||||
|
||||
const empty = table.packType(&.{});
|
||||
try std.testing.expectEqualStrings("pack()", table.formatTypeName(arena.allocator(), empty));
|
||||
@@ -266,7 +266,7 @@ test "isUnsignedInt: builtin signedness classification" {
|
||||
}
|
||||
// Signed / non-integer builtins are not unsigned.
|
||||
inline for (.{
|
||||
TypeId.s8, TypeId.s16, TypeId.s32, TypeId.s64, TypeId.isize,
|
||||
TypeId.i8, TypeId.i16, TypeId.i32, TypeId.i64, TypeId.isize,
|
||||
TypeId.bool, TypeId.f32, TypeId.f64, TypeId.string,
|
||||
TypeId.void, TypeId.any, TypeId.unresolved,
|
||||
}) |ty| {
|
||||
@@ -280,9 +280,9 @@ test "isUnsignedInt: user-defined arbitrary-width ints" {
|
||||
defer table.deinit();
|
||||
|
||||
const u24_ty = table.intern(.{ .unsigned = 24 });
|
||||
const s24_ty = table.intern(.{ .signed = 24 });
|
||||
const i24_ty = table.intern(.{ .signed = 24 });
|
||||
try std.testing.expect(table.isUnsignedInt(u24_ty));
|
||||
try std.testing.expect(!table.isUnsignedInt(s24_ty));
|
||||
try std.testing.expect(!table.isUnsignedInt(i24_ty));
|
||||
|
||||
// A non-integer user type is never unsigned.
|
||||
const ptr_ty = table.ptrTo(.u32);
|
||||
@@ -303,8 +303,8 @@ test "phase D: forward-decl field fill preserves intern key" {
|
||||
|
||||
// Full definition arrives later; same name (and nominal id) → same key.
|
||||
const fields = [_]TypeInfo.StructInfo.Field{
|
||||
.{ .name = table.internString("x"), .ty = .s64 },
|
||||
.{ .name = table.internString("y"), .ty = .s64 },
|
||||
.{ .name = table.internString("x"), .ty = .i64 },
|
||||
.{ .name = table.internString("y"), .ty = .i64 },
|
||||
};
|
||||
table.updatePreservingKey(id, .{ .@"struct" = .{ .name = foo, .fields = &fields } });
|
||||
|
||||
@@ -322,7 +322,7 @@ test "phase D: anon rename re-keys intern_map" {
|
||||
|
||||
const anon = table.internString("__anon");
|
||||
const fields = [_]TypeInfo.StructInfo.Field{
|
||||
.{ .name = table.internString("x"), .ty = .s64 },
|
||||
.{ .name = table.internString("x"), .ty = .i64 },
|
||||
};
|
||||
const id = table.internNominal(.{ .@"struct" = .{ .name = anon, .fields = &fields } }, 0);
|
||||
try std.testing.expectEqual(id, table.findByName(anon).?);
|
||||
@@ -387,13 +387,13 @@ test "phase D: parameterized protocol value struct interns stably" {
|
||||
defer table.deinit();
|
||||
|
||||
// `instantiateParamProtocol` registers a `{ctx, __vtable}` value struct
|
||||
// under a mangled name (e.g. `VL__s64`). Same instantiation → same id.
|
||||
// under a mangled name (e.g. `VL__i64`). Same instantiation → same id.
|
||||
const void_ptr = table.ptrTo(.void);
|
||||
const fields = [_]TypeInfo.StructInfo.Field{
|
||||
.{ .name = table.internString("ctx"), .ty = void_ptr },
|
||||
.{ .name = table.internString("__vtable"), .ty = void_ptr },
|
||||
};
|
||||
const info: TypeInfo = .{ .@"struct" = .{ .name = table.internString("VL__s64"), .fields = &fields, .is_protocol = true } };
|
||||
const info: TypeInfo = .{ .@"struct" = .{ .name = table.internString("VL__i64"), .fields = &fields, .is_protocol = true } };
|
||||
const a = table.intern(info);
|
||||
const b = table.intern(info);
|
||||
try std.testing.expectEqual(a, b);
|
||||
@@ -408,7 +408,7 @@ test "phase D: same display-name distinct nominal ids" {
|
||||
defer table.deinit();
|
||||
|
||||
const foo = table.internString("Foo");
|
||||
const f = [_]TypeInfo.StructInfo.Field{.{ .name = table.internString("x"), .ty = .s64 }};
|
||||
const f = [_]TypeInfo.StructInfo.Field{.{ .name = table.internString("x"), .ty = .i64 }};
|
||||
const base: TypeInfo = .{ .@"struct" = .{ .name = foo, .fields = &f } };
|
||||
|
||||
const a = table.internNominal(base, 1);
|
||||
@@ -445,7 +445,7 @@ test "phase D: internNominal(.,0) is byte-identical to legacy intern (old==new)"
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
|
||||
const f = [_]TypeInfo.StructInfo.Field{.{ .name = table.internString("x"), .ty = .s64 }};
|
||||
const f = [_]TypeInfo.StructInfo.Field{.{ .name = table.internString("x"), .ty = .i64 }};
|
||||
const variants = [_]types.StringId{table.internString("v")};
|
||||
const tags = [_]u32{7};
|
||||
|
||||
@@ -453,7 +453,7 @@ test "phase D: internNominal(.,0) is byte-identical to legacy intern (old==new)"
|
||||
.{ .@"struct" = .{ .name = table.internString("S"), .fields = &f } },
|
||||
.{ .@"enum" = .{ .name = table.internString("E"), .variants = &variants } },
|
||||
.{ .@"union" = .{ .name = table.internString("U"), .fields = &f } },
|
||||
.{ .tagged_union = .{ .name = table.internString("T"), .fields = &f, .tag_type = .s64 } },
|
||||
.{ .tagged_union = .{ .name = table.internString("T"), .fields = &f, .tag_type = .i64 } },
|
||||
.{ .error_set = .{ .name = table.internString("Err"), .tags = &tags } },
|
||||
};
|
||||
for (cases) |info| {
|
||||
|
||||
@@ -9,7 +9,7 @@ pub const TypeId = enum(u32) {
|
||||
// Builtin slots 0–17.
|
||||
/// Resolution failed (e.g. an unannotated param whose type was never
|
||||
/// inferred from context). A dedicated sentinel — never a legitimate
|
||||
/// result — so downstream `== .void`/`== .s64` checks can't silently
|
||||
/// result — so downstream `== .void`/`== .i64` checks can't silently
|
||||
/// swallow it. Must never reach codegen; sizeOf/toLLVMType panic on it.
|
||||
///
|
||||
/// Deliberately slot 0: a zero-initialised or forgotten `TypeId` (the most
|
||||
@@ -17,10 +17,10 @@ pub const TypeId = enum(u32) {
|
||||
/// tripwire, rather than silently masquerading as `.void`.
|
||||
unresolved = 0,
|
||||
bool = 1,
|
||||
s8 = 2,
|
||||
s16 = 3,
|
||||
s32 = 4,
|
||||
s64 = 5,
|
||||
i8 = 2,
|
||||
i16 = 3,
|
||||
i32 = 4,
|
||||
i64 = 5,
|
||||
u8 = 6,
|
||||
u16 = 7,
|
||||
u32 = 8,
|
||||
@@ -125,7 +125,7 @@ pub const TypeInfo = union(enum) {
|
||||
pub const TaggedUnionInfo = struct {
|
||||
name: StringId,
|
||||
fields: []const StructInfo.Field,
|
||||
tag_type: TypeId, // tag integer type (e.g. .u32, .s64)
|
||||
tag_type: TypeId, // tag integer type (e.g. .u32, .i64)
|
||||
backing_type: ?TypeId = null, // enum struct backing (e.g. { tag: u32; _: u32; payload: [30]u32; })
|
||||
explicit_tag_values: ?[]const i64 = null, // explicit variant values (e.g., quit :: 0x100)
|
||||
nominal_id: u32 = 0, // stable nominal identity; 0 == structural (legacy)
|
||||
@@ -366,10 +366,10 @@ pub const TypeTable = struct {
|
||||
const builtins = [_]TypeInfo{
|
||||
.unresolved, // 0: resolution-failure sentinel
|
||||
.bool, // 1
|
||||
.{ .signed = 8 }, // 2: s8
|
||||
.{ .signed = 16 }, // 3: s16
|
||||
.{ .signed = 32 }, // 4: s32
|
||||
.{ .signed = 64 }, // 5: s64
|
||||
.{ .signed = 8 }, // 2: i8
|
||||
.{ .signed = 16 }, // 3: i16
|
||||
.{ .signed = 32 }, // 4: i32
|
||||
.{ .signed = 64 }, // 5: i64
|
||||
.{ .unsigned = 8 }, // 6: u8
|
||||
.{ .unsigned = 16 }, // 7: u16
|
||||
.{ .unsigned = 32 }, // 8: u32
|
||||
@@ -662,11 +662,11 @@ pub const TypeTable = struct {
|
||||
/// True iff `ty` is an unsigned integer — a builtin (u8/u16/u32/u64/usize)
|
||||
/// or a user-defined arbitrary-width unsigned int. Canonical signedness
|
||||
/// query for reflection (`type_is_unsigned`) and the `{}` formatter so a
|
||||
/// u64 value renders as unsigned decimal rather than the s64 reinterpretation.
|
||||
/// u64 value renders as unsigned decimal rather than the i64 reinterpretation.
|
||||
pub fn isUnsignedInt(self: *const TypeTable, ty: TypeId) bool {
|
||||
switch (ty) {
|
||||
.u8, .u16, .u32, .u64, .usize => return true,
|
||||
.bool, .s8, .s16, .s32, .s64, .isize => return false,
|
||||
.bool, .i8, .i16, .i32, .i64, .isize => return false,
|
||||
else => {},
|
||||
}
|
||||
if (ty.isBuiltin()) return false;
|
||||
@@ -677,10 +677,10 @@ pub const TypeTable = struct {
|
||||
const ptr_size: usize = self.pointer_size;
|
||||
if (ty == .void) return 0;
|
||||
if (ty == .bool) return 1;
|
||||
if (ty == .u8 or ty == .s8) return 1;
|
||||
if (ty == .u16 or ty == .s16) return 2;
|
||||
if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
|
||||
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
|
||||
if (ty == .u8 or ty == .i8) return 1;
|
||||
if (ty == .u16 or ty == .i16) return 2;
|
||||
if (ty == .i32 or ty == .u32 or ty == .f32) return 4;
|
||||
if (ty == .i64 or ty == .u64 or ty == .f64) return 8;
|
||||
if (ty == .usize or ty == .isize) return ptr_size;
|
||||
if (ty == .string) return 16; // {ptr, i64} — always 16 (i64 alignment pads on wasm32)
|
||||
if (ty == .any) return 16; // {i64 tag, i64 value} — Any boxed layout
|
||||
@@ -777,10 +777,10 @@ pub const TypeTable = struct {
|
||||
const ptr_align: usize = self.pointer_size;
|
||||
if (ty == .void) return 1;
|
||||
if (ty == .bool) return 1;
|
||||
if (ty == .u8 or ty == .s8) return 1;
|
||||
if (ty == .u16 or ty == .s16) return 2;
|
||||
if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
|
||||
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
|
||||
if (ty == .u8 or ty == .i8) return 1;
|
||||
if (ty == .u16 or ty == .i16) return 2;
|
||||
if (ty == .i32 or ty == .u32 or ty == .f32) return 4;
|
||||
if (ty == .i64 or ty == .u64 or ty == .f64) return 8;
|
||||
if (ty == .usize or ty == .isize) return ptr_align;
|
||||
if (ty == .string) return 8; // i64 drives alignment
|
||||
if (ty == .any) return 8; // {i64, i64} aligns to 8
|
||||
@@ -836,16 +836,16 @@ pub const TypeTable = struct {
|
||||
return self.strings.get(id);
|
||||
}
|
||||
|
||||
/// Format a TypeId for display (e.g., "s32", "*bool", "[]u8").
|
||||
/// Format a TypeId for display (e.g., "i32", "*bool", "[]u8").
|
||||
pub fn typeName(self: *const TypeTable, id: TypeId) []const u8 {
|
||||
// Fast path for builtins
|
||||
return switch (id) {
|
||||
.void => "void",
|
||||
.bool => "bool",
|
||||
.s8 => "s8",
|
||||
.s16 => "s16",
|
||||
.s32 => "s32",
|
||||
.s64 => "s64",
|
||||
.i8 => "i8",
|
||||
.i16 => "i16",
|
||||
.i32 => "i32",
|
||||
.i64 => "i64",
|
||||
.u8 => "u8",
|
||||
.u16 => "u16",
|
||||
.u32 => "u32",
|
||||
@@ -965,7 +965,7 @@ pub const TypeTable = struct {
|
||||
buf.append(alloc, ')') catch break :blk "pack(?)";
|
||||
break :blk buf.toOwnedSlice(alloc) catch "pack(?)";
|
||||
},
|
||||
.signed => |w| std.fmt.allocPrint(alloc, "s{d}", .{w}) catch "s?",
|
||||
.signed => |w| std.fmt.allocPrint(alloc, "i{d}", .{w}) catch "i?",
|
||||
.unsigned => |w| std.fmt.allocPrint(alloc, "u{d}", .{w}) catch "u?",
|
||||
else => self.typeName(id),
|
||||
};
|
||||
|
||||
@@ -54,7 +54,7 @@ pub const Lexer = struct {
|
||||
// following identifier to be RAW (never type-classified, never
|
||||
// reserved-checked). The emitted token's span excludes the backtick, so
|
||||
// its text is the bare name, and a backticked keyword spelling
|
||||
// (`` `s2 ``, `` `string ``) is still an `.identifier`, never a keyword.
|
||||
// (`` `i2 ``, `` `string ``) is still an `.identifier`, never a keyword.
|
||||
if (c == '`') {
|
||||
const id_start = start + 1;
|
||||
if (id_start < self.source.len and isIdentStart(self.source[id_start])) {
|
||||
@@ -538,22 +538,22 @@ test "lex keywords" {
|
||||
}
|
||||
|
||||
test "lex type-like identifiers" {
|
||||
// s32, u8, bool, string are identifiers, not keywords
|
||||
var lex = Lexer.init("s32 u8 bool string");
|
||||
// i32, u8, bool, string are identifiers, not keywords
|
||||
var lex = Lexer.init("i32 u8 bool string");
|
||||
for (0..4) |_| {
|
||||
try std.testing.expectEqual(Tag.identifier, lex.next().tag);
|
||||
}
|
||||
}
|
||||
|
||||
test "lex backtick raw identifier" {
|
||||
const source: [:0]const u8 = "`s2 `string `for";
|
||||
const source: [:0]const u8 = "`i2 `string `for";
|
||||
var lex = Lexer.init(source);
|
||||
// Each is an `.identifier` carrying `is_raw`, even a keyword spelling
|
||||
// (`for`), with text that excludes the leading backtick.
|
||||
const t1 = lex.next();
|
||||
try std.testing.expectEqual(Tag.identifier, t1.tag);
|
||||
try std.testing.expect(t1.is_raw);
|
||||
try std.testing.expectEqualStrings("s2", t1.slice(source));
|
||||
try std.testing.expectEqualStrings("i2", t1.slice(source));
|
||||
const t2 = lex.next();
|
||||
try std.testing.expectEqual(Tag.identifier, t2.tag);
|
||||
try std.testing.expect(t2.is_raw);
|
||||
@@ -566,7 +566,7 @@ test "lex backtick raw identifier" {
|
||||
}
|
||||
|
||||
test "lex bare identifier is not raw" {
|
||||
var lex = Lexer.init("s2");
|
||||
var lex = Lexer.init("i2");
|
||||
const tok = lex.next();
|
||||
try std.testing.expectEqual(Tag.identifier, tok.tag);
|
||||
try std.testing.expect(!tok.is_raw);
|
||||
|
||||
@@ -646,16 +646,16 @@ pub const Server = struct {
|
||||
const builtins = [_]struct { label: []const u8, detail: []const u8 }{
|
||||
.{ .label = "type_of", .detail = "(val: $T) -> Type" },
|
||||
.{ .label = "type_name", .detail = "($T: Type) -> string" },
|
||||
.{ .label = "field_count", .detail = "($T: Type) -> s32" },
|
||||
.{ .label = "field_name", .detail = "($T: Type, idx: s32) -> string" },
|
||||
.{ .label = "field_value", .detail = "(s: $T, idx: s32) -> Any" },
|
||||
.{ .label = "size_of", .detail = "($T: Type) -> s64" },
|
||||
.{ .label = "align_of", .detail = "($T: Type) -> s64" },
|
||||
.{ .label = "field_count", .detail = "($T: Type) -> i32" },
|
||||
.{ .label = "field_name", .detail = "($T: Type, idx: i32) -> string" },
|
||||
.{ .label = "field_value", .detail = "(s: $T, idx: i32) -> Any" },
|
||||
.{ .label = "size_of", .detail = "($T: Type) -> i64" },
|
||||
.{ .label = "align_of", .detail = "($T: Type) -> i64" },
|
||||
.{ .label = "cast", .detail = "(Type) expr — prefix type cast" },
|
||||
.{ .label = "malloc", .detail = "(size: s64) -> *void" },
|
||||
.{ .label = "malloc", .detail = "(size: i64) -> *void" },
|
||||
.{ .label = "free", .detail = "(ptr: *void) -> void" },
|
||||
.{ .label = "memcpy", .detail = "(dst: *void, src: *void, size: s64) -> *void" },
|
||||
.{ .label = "memset", .detail = "(dst: *void, val: s64, size: s64) -> void" },
|
||||
.{ .label = "memcpy", .detail = "(dst: *void, src: *void, size: i64) -> *void" },
|
||||
.{ .label = "memset", .detail = "(dst: *void, val: i64, size: i64) -> void" },
|
||||
.{ .label = "sqrt", .detail = "(x: $T) -> T" },
|
||||
};
|
||||
for (&keywords) |kw| {
|
||||
@@ -1116,16 +1116,16 @@ pub const Server = struct {
|
||||
const builtin_sigs = [_]struct { name: []const u8, label: []const u8, params: []const []const u8 }{
|
||||
.{ .name = "type_of", .label = "type_of(val: $T) -> Type", .params = &.{"val: $T"} },
|
||||
.{ .name = "type_name", .label = "type_name($T: Type) -> string", .params = &.{"$T: Type"} },
|
||||
.{ .name = "field_count", .label = "field_count($T: Type) -> s32", .params = &.{"$T: Type"} },
|
||||
.{ .name = "field_name", .label = "field_name($T: Type, idx: s32) -> string", .params = &.{ "$T: Type", "idx: s32" } },
|
||||
.{ .name = "field_value", .label = "field_value(s: $T, idx: s32) -> Any", .params = &.{ "s: $T", "idx: s32" } },
|
||||
.{ .name = "size_of", .label = "size_of($T: Type) -> s64", .params = &.{"$T: Type"} },
|
||||
.{ .name = "align_of", .label = "align_of($T: Type) -> s64", .params = &.{"$T: Type"} },
|
||||
.{ .name = "field_count", .label = "field_count($T: Type) -> i32", .params = &.{"$T: Type"} },
|
||||
.{ .name = "field_name", .label = "field_name($T: Type, idx: i32) -> string", .params = &.{ "$T: Type", "idx: i32" } },
|
||||
.{ .name = "field_value", .label = "field_value(s: $T, idx: i32) -> Any", .params = &.{ "s: $T", "idx: i32" } },
|
||||
.{ .name = "size_of", .label = "size_of($T: Type) -> i64", .params = &.{"$T: Type"} },
|
||||
.{ .name = "align_of", .label = "align_of($T: Type) -> i64", .params = &.{"$T: Type"} },
|
||||
.{ .name = "cast", .label = "cast(Type) expr", .params = &.{"Type"} },
|
||||
.{ .name = "malloc", .label = "malloc(size: s64) -> *void", .params = &.{"size: s64"} },
|
||||
.{ .name = "malloc", .label = "malloc(size: i64) -> *void", .params = &.{"size: i64"} },
|
||||
.{ .name = "free", .label = "free(ptr: *void) -> void", .params = &.{"ptr: *void"} },
|
||||
.{ .name = "memcpy", .label = "memcpy(dst: *void, src: *void, size: s64) -> *void", .params = &.{ "dst: *void", "src: *void", "size: s64" } },
|
||||
.{ .name = "memset", .label = "memset(dst: *void, val: s64, size: s64) -> void", .params = &.{ "dst: *void", "val: s64", "size: s64" } },
|
||||
.{ .name = "memcpy", .label = "memcpy(dst: *void, src: *void, size: i64) -> *void", .params = &.{ "dst: *void", "src: *void", "size: i64" } },
|
||||
.{ .name = "memset", .label = "memset(dst: *void, val: i64, size: i64) -> void", .params = &.{ "dst: *void", "val: i64", "size: i64" } },
|
||||
.{ .name = "sqrt", .label = "sqrt(x: $T) -> T", .params = &.{"x: $T"} },
|
||||
.{ .name = "print", .label = "print(fmt: string, args: ..Any)", .params = &.{ "fmt: string", "args: ..Any" } },
|
||||
.{ .name = "out", .label = "out(str: string) -> void", .params = &.{"str: string"} },
|
||||
@@ -3188,7 +3188,7 @@ test "analyzeDocument: parse and sema basic function" {
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var store = doc_mod.DocumentStore.init(alloc, test_io(), &.{});
|
||||
const src: [:0]const u8 = "add :: (a: s32, b: s32) -> s32 { a + b; }";
|
||||
const src: [:0]const u8 = "add :: (a: i32, b: i32) -> i32 { a + b; }";
|
||||
const doc = try store.openOrUpdate("main.sx", src, 1);
|
||||
try store.analyzeDocument(doc);
|
||||
|
||||
@@ -3207,14 +3207,14 @@ test "analyzeDocument: flat import pre-registers symbols" {
|
||||
var store = doc_mod.DocumentStore.init(alloc, test_io(), &.{});
|
||||
|
||||
// Pre-load the imported module
|
||||
const lib_src: [:0]const u8 = "mul :: (a: s32, b: s32) -> s32 { a * b; }";
|
||||
const lib_src: [:0]const u8 = "mul :: (a: i32, b: i32) -> i32 { a * b; }";
|
||||
const lib_doc = try store.openOrUpdate("lib.sx", lib_src, 1);
|
||||
try store.analyzeDocument(lib_doc);
|
||||
|
||||
// Load main file with flat import
|
||||
const main_src: [:0]const u8 =
|
||||
\\#import "lib.sx";
|
||||
\\main :: () -> s32 { mul(3, 4); }
|
||||
\\main :: () -> i32 { mul(3, 4); }
|
||||
;
|
||||
const main_doc = try store.openOrUpdate("main.sx", main_src, 1);
|
||||
try store.analyzeDocument(main_doc);
|
||||
@@ -3234,14 +3234,14 @@ test "analyzeDocument: namespaced import registers namespace symbol" {
|
||||
var store = doc_mod.DocumentStore.init(alloc, test_io(), &.{});
|
||||
|
||||
// Pre-load the imported module
|
||||
const lib_src: [:0]const u8 = "add :: (a: s32, b: s32) -> s32 { a + b; }";
|
||||
const lib_src: [:0]const u8 = "add :: (a: i32, b: i32) -> i32 { a + b; }";
|
||||
const lib_doc = try store.openOrUpdate("lib.sx", lib_src, 1);
|
||||
try store.analyzeDocument(lib_doc);
|
||||
|
||||
// Load main file with namespaced import
|
||||
const main_src: [:0]const u8 =
|
||||
\\pkg :: #import "lib.sx";
|
||||
\\main :: () -> s32 { pkg.add(3, 4); }
|
||||
\\main :: () -> i32 { pkg.add(3, 4); }
|
||||
;
|
||||
const main_doc = try store.openOrUpdate("main.sx", main_src, 1);
|
||||
try store.analyzeDocument(main_doc);
|
||||
@@ -3260,13 +3260,13 @@ test "analyzeDocument: namespaced import fn_signatures have prefix" {
|
||||
|
||||
var store = doc_mod.DocumentStore.init(alloc, test_io(), &.{});
|
||||
|
||||
const lib_src: [:0]const u8 = "add :: (a: s32, b: s32) -> s32 { a + b; }";
|
||||
const lib_src: [:0]const u8 = "add :: (a: i32, b: i32) -> i32 { a + b; }";
|
||||
const lib_doc = try store.openOrUpdate("lib.sx", lib_src, 1);
|
||||
try store.analyzeDocument(lib_doc);
|
||||
|
||||
const main_src: [:0]const u8 =
|
||||
\\pkg :: #import "lib.sx";
|
||||
\\main :: () -> s32 { pkg.add(1, 2); }
|
||||
\\main :: () -> i32 { pkg.add(1, 2); }
|
||||
;
|
||||
const main_doc = try store.openOrUpdate("main.sx", main_src, 1);
|
||||
try store.analyzeDocument(main_doc);
|
||||
@@ -3283,14 +3283,14 @@ test "analyzeDocument: pipe operator desugars to call" {
|
||||
|
||||
var store = doc_mod.DocumentStore.init(alloc, test_io(), &.{});
|
||||
|
||||
const lib_src: [:0]const u8 = "add :: (a: s32, b: s32) -> s32 { a + b; }";
|
||||
const lib_src: [:0]const u8 = "add :: (a: i32, b: i32) -> i32 { a + b; }";
|
||||
const lib_doc = try store.openOrUpdate("lib.sx", lib_src, 1);
|
||||
try store.analyzeDocument(lib_doc);
|
||||
|
||||
// Pipe operator should parse and analyze without errors
|
||||
const main_src: [:0]const u8 =
|
||||
\\pkg :: #import "lib.sx";
|
||||
\\main :: () -> s32 { 3 |> pkg.add(4); }
|
||||
\\main :: () -> i32 { 3 |> pkg.add(4); }
|
||||
;
|
||||
const main_doc = try store.openOrUpdate("main.sx", main_src, 1);
|
||||
try store.analyzeDocument(main_doc);
|
||||
@@ -3309,7 +3309,7 @@ test "analyzeDocument: for-loop capture variables are registered" {
|
||||
|
||||
const src: [:0]const u8 =
|
||||
\\main :: () {
|
||||
\\ arr : [3]s32 = ---;
|
||||
\\ arr : [3]i32 = ---;
|
||||
\\ for arr, 0.. (it, ix) {
|
||||
\\ x := it + ix;
|
||||
\\ }
|
||||
@@ -3335,7 +3335,7 @@ test "analyzeDocument: for-loop with underscore capture" {
|
||||
|
||||
const src: [:0]const u8 =
|
||||
\\main :: () {
|
||||
\\ arr : [3]s32 = ---;
|
||||
\\ arr : [3]i32 = ---;
|
||||
\\ for arr, 0.. (_, ix) {
|
||||
\\ x := ix;
|
||||
\\ }
|
||||
@@ -3360,7 +3360,7 @@ test "analyzeDocument: for-loop value-only capture" {
|
||||
|
||||
const src: [:0]const u8 =
|
||||
\\main :: () {
|
||||
\\ arr : [3]s32 = ---;
|
||||
\\ arr : [3]i32 = ---;
|
||||
\\ for arr (val) {
|
||||
\\ x := val;
|
||||
\\ }
|
||||
@@ -3393,13 +3393,13 @@ test "lsp/references: a field's uses are found across documents" {
|
||||
|
||||
var store = doc_mod.DocumentStore.init(alloc, test_io(), &.{});
|
||||
|
||||
const lib_src: [:0]const u8 = "Move :: struct { flag: s64; }";
|
||||
const lib_src: [:0]const u8 = "Move :: struct { flag: i64; }";
|
||||
const lib_doc = try store.openOrUpdate("lib.sx", lib_src, 1);
|
||||
try store.analyzeDocument(lib_doc);
|
||||
|
||||
const main_src: [:0]const u8 =
|
||||
\\#import "lib.sx";
|
||||
\\use :: (m: *Move) -> s64 { m.flag; }
|
||||
\\use :: (m: *Move) -> i64 { m.flag; }
|
||||
;
|
||||
const main_doc = try store.openOrUpdate("main.sx", main_src, 1);
|
||||
try store.analyzeDocument(main_doc);
|
||||
@@ -3423,7 +3423,7 @@ test "lsp/references: excluding the declaration drops the definition" {
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var store = doc_mod.DocumentStore.init(alloc, test_io(), &.{});
|
||||
const src: [:0]const u8 = "Move :: struct { flag: s64; } use :: (m: *Move) -> s64 { m.flag; }";
|
||||
const src: [:0]const u8 = "Move :: struct { flag: i64; } use :: (m: *Move) -> i64 { m.flag; }";
|
||||
const doc = try store.openOrUpdate("main.sx", src, 1);
|
||||
try store.analyzeDocument(doc);
|
||||
|
||||
@@ -3442,7 +3442,7 @@ test "lsp/definition: cursor on a member declaration resolves to itself" {
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var store = doc_mod.DocumentStore.init(alloc, test_io(), &.{});
|
||||
const src: [:0]const u8 = "Move :: struct { flag: s64; }";
|
||||
const src: [:0]const u8 = "Move :: struct { flag: i64; }";
|
||||
const doc = try store.openOrUpdate("main.sx", src, 1);
|
||||
try store.analyzeDocument(doc);
|
||||
const sema = doc.sema orelse return error.SkipZigTest;
|
||||
@@ -3465,8 +3465,8 @@ test "lsp/inlayHint: a for-loop capture in a struct method shows its element typ
|
||||
|
||||
var store = doc_mod.DocumentStore.init(alloc, test_io(), &.{});
|
||||
const lib_src: [:0]const u8 =
|
||||
\\Move :: struct { flag: s64; }
|
||||
\\List :: struct ($T: Type) { items: [*]T = null; len: s64 = 0; }
|
||||
\\Move :: struct { flag: i64; }
|
||||
\\List :: struct ($T: Type) { items: [*]T = null; len: i64 = 0; }
|
||||
;
|
||||
const lib_doc = try store.openOrUpdate("lib.sx", lib_src, 1);
|
||||
try store.analyzeDocument(lib_doc);
|
||||
@@ -3509,8 +3509,8 @@ test "lsp/workspace: loadWorkspaceFiles analyses .sx files that were never opene
|
||||
std.Io.Dir.deleteFile(.cwd(), io, dir ++ "/b.sx") catch {};
|
||||
std.Io.Dir.deleteDir(.cwd(), io, dir) catch {};
|
||||
}
|
||||
try std.Io.Dir.writeFile(.cwd(), io, .{ .sub_path = dir ++ "/a.sx", .data = "Foo :: struct { x: s64; }" });
|
||||
try std.Io.Dir.writeFile(.cwd(), io, .{ .sub_path = dir ++ "/b.sx", .data = "Bar :: struct { y: s64; }" });
|
||||
try std.Io.Dir.writeFile(.cwd(), io, .{ .sub_path = dir ++ "/a.sx", .data = "Foo :: struct { x: i64; }" });
|
||||
try std.Io.Dir.writeFile(.cwd(), io, .{ .sub_path = dir ++ "/b.sx", .data = "Bar :: struct { y: i64; }" });
|
||||
|
||||
var store = doc_mod.DocumentStore.init(alloc, io, &.{});
|
||||
store.root_path = dir;
|
||||
@@ -3541,12 +3541,12 @@ test "lsp/project: whole-program check attributes a reachable error to its modul
|
||||
// only sees because `main` calls `use` (reachability). Lowering mod.sx alone
|
||||
// would miss it; the whole-program check catches it and pins it to mod.sx.
|
||||
try std.Io.Dir.writeFile(.cwd(), io, .{ .sub_path = dir ++ "/mod.sx", .data =
|
||||
"Move :: struct { flag: s64; }\n" ++
|
||||
"take :: (m: Move) -> s64 { return m.flag; }\n" ++
|
||||
"use :: (p: *Move) -> s64 { return take(p); }\n" });
|
||||
"Move :: struct { flag: i64; }\n" ++
|
||||
"take :: (m: Move) -> i64 { return m.flag; }\n" ++
|
||||
"use :: (p: *Move) -> i64 { return take(p); }\n" });
|
||||
try std.Io.Dir.writeFile(.cwd(), io, .{ .sub_path = dir ++ "/main.sx", .data =
|
||||
"#import \"mod.sx\";\n" ++
|
||||
"main :: () -> s32 { mv : Move = .{ flag = 1 }; return xx use(@mv); }\n" });
|
||||
"main :: () -> i32 { mv : Move = .{ flag = 1 }; return xx use(@mv); }\n" });
|
||||
|
||||
const store = doc_mod.DocumentStore.init(alloc, io, &.{});
|
||||
var server = Server{
|
||||
|
||||
@@ -640,14 +640,14 @@ pub const Parser = struct {
|
||||
}
|
||||
|
||||
if (self.current.tag.isTypeKeyword() or self.isIdentLike()) {
|
||||
// A backtick raw identifier (`` `s2 ``) in type position is the
|
||||
// LITERAL name `s2` used as a type reference — never the builtin /
|
||||
// A backtick raw identifier (`` `i2 ``) in type position is the
|
||||
// LITERAL name `i2` used as a type reference — never the builtin /
|
||||
// reserved keyword. The raw flag rides the type ATOM through the
|
||||
// SAME qualified-path / `Closure` / parameterized continuations as a
|
||||
// bare name (so `` `s2(s64) ``, `` `s2.Inner ``, `` *`s2 `` all
|
||||
// bare name (so `` `i2(i64) ``, `` `i2.Inner ``, `` *`i2 `` all
|
||||
// parse); it is threaded onto the final `type_expr` /
|
||||
// `parameterized_type_expr` so resolution skips the builtin
|
||||
// classifier and looks up a `` `s2 ``-declared type.
|
||||
// classifier and looks up a `` `i2 ``-declared type.
|
||||
const atom_is_raw = self.current.is_raw;
|
||||
var name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
@@ -2787,10 +2787,10 @@ pub const Parser = struct {
|
||||
.identifier => {
|
||||
const name = self.tokenSlice(self.current);
|
||||
const is_raw = self.current.is_raw;
|
||||
// A backtick raw identifier (`` `s2 ``) is NEVER type-classified —
|
||||
// A backtick raw identifier (`` `i2 ``) is NEVER type-classified —
|
||||
// it is always a value identifier, bypassing the reserved-type-name
|
||||
// rule. Only a bare spelling is checked for a type name
|
||||
// (e.g. s32, u8, s128).
|
||||
// (e.g. i32, u8, s128).
|
||||
if (!is_raw and Type.fromName(name) != null) {
|
||||
self.advance();
|
||||
return try self.createNode(start, .{ .type_expr = .{ .name = name } });
|
||||
@@ -2900,7 +2900,7 @@ pub const Parser = struct {
|
||||
return try self.parseEnumDecl("__anon", start, false);
|
||||
},
|
||||
.kw_union => {
|
||||
// Anonymous C-style union expression: union { f: f32; i: s32; }
|
||||
// Anonymous C-style union expression: union { f: f32; i: i32; }
|
||||
return try self.parseUnionDecl("__anon", start, false);
|
||||
},
|
||||
.kw_if => {
|
||||
@@ -3978,7 +3978,7 @@ test "parse minimal main" {
|
||||
test "block value: trailing expr without `;` produces a value" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), "f :: () -> s32 { 42 }");
|
||||
var parser = Parser.init(arena.allocator(), "f :: () -> i32 { 42 }");
|
||||
const root = try parser.parse();
|
||||
const body = root.data.root.decls[0].data.fn_decl.body;
|
||||
try std.testing.expect(body.data.block.produces_value);
|
||||
@@ -3988,7 +3988,7 @@ test "block value: trailing expr without `;` produces a value" {
|
||||
test "block value: trailing `;` discards the value" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), "f :: () -> s32 { 42; }");
|
||||
var parser = Parser.init(arena.allocator(), "f :: () -> i32 { 42; }");
|
||||
const root = try parser.parse();
|
||||
const body = root.data.root.decls[0].data.fn_decl.body;
|
||||
try std.testing.expect(!body.data.block.produces_value);
|
||||
@@ -3998,7 +3998,7 @@ test "block value: trailing `;` discards the value" {
|
||||
test "block value: match arms are exempt (keep `;`, still produce a value)" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), "f :: (n: s32) -> s32 { if n == { case 1: 5; else: 0; } }");
|
||||
var parser = Parser.init(arena.allocator(), "f :: (n: i32) -> i32 { if n == { case 1: 5; else: 0; } }");
|
||||
const root = try parser.parse();
|
||||
const body = root.data.root.decls[0].data.fn_decl.body;
|
||||
// Function body's trailing match has no `;` → the body is a value.
|
||||
@@ -4092,7 +4092,7 @@ test "parse void function with builtin body" {
|
||||
}
|
||||
|
||||
test "parse void function with foreign body" {
|
||||
const source = "InitWindow :: (width: s32, height: s32, title: *u8) -> void #foreign rl;";
|
||||
const source = "InitWindow :: (width: i32, height: i32, title: *u8) -> void #foreign rl;";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), source);
|
||||
@@ -4170,7 +4170,7 @@ test "parse lambda with generic params" {
|
||||
}
|
||||
|
||||
test "parse lambda with return type" {
|
||||
const source = "f :: (x: s32) -> s32 => x;";
|
||||
const source = "f :: (x: i32) -> i32 => x;";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), source);
|
||||
@@ -4180,7 +4180,7 @@ test "parse lambda with return type" {
|
||||
const fd = decl.data.fn_decl;
|
||||
try std.testing.expect(fd.return_type != null);
|
||||
try std.testing.expect(fd.return_type.?.data == .type_expr);
|
||||
try std.testing.expectEqualStrings("s32", fd.return_type.?.data.type_expr.name);
|
||||
try std.testing.expectEqualStrings("i32", fd.return_type.?.data.type_expr.name);
|
||||
}
|
||||
|
||||
test "parse match with else arm" {
|
||||
@@ -4315,7 +4315,7 @@ test "parse pack expansion: tuple type (..F(Ts))" {
|
||||
}
|
||||
|
||||
test "parse pack expansion: closure sig projection Closure(..sources.T)" {
|
||||
const source = "h :: (cb: Closure(..sources.T) -> s32) => cb;";
|
||||
const source = "h :: (cb: Closure(..sources.T) -> i32) => cb;";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), source);
|
||||
@@ -4327,7 +4327,7 @@ test "parse pack expansion: closure sig projection Closure(..sources.T)" {
|
||||
}
|
||||
|
||||
test "parse closure sig bare pack Closure(..Ts) has no projection" {
|
||||
const source = "j :: (cb: Closure(..Ts) -> s32) => cb;";
|
||||
const source = "j :: (cb: Closure(..Ts) -> i32) => cb;";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), source);
|
||||
@@ -4405,7 +4405,7 @@ test "parse bare failable return: named `!Foo`" {
|
||||
}
|
||||
|
||||
test "parse multi-return with inferred `!` as trailing element" {
|
||||
const source = "f :: () -> (s32, !) { 0; }";
|
||||
const source = "f :: () -> (i32, !) { 0; }";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), source);
|
||||
@@ -4415,13 +4415,13 @@ test "parse multi-return with inferred `!` as trailing element" {
|
||||
const fields = rt.data.tuple_type_expr.field_types;
|
||||
try std.testing.expectEqual(@as(usize, 2), fields.len);
|
||||
try std.testing.expect(fields[0].data == .type_expr);
|
||||
try std.testing.expectEqualStrings("s32", fields[0].data.type_expr.name);
|
||||
try std.testing.expectEqualStrings("i32", fields[0].data.type_expr.name);
|
||||
try std.testing.expect(fields[1].data == .error_type_expr);
|
||||
try std.testing.expect(fields[1].data.error_type_expr.name == null);
|
||||
}
|
||||
|
||||
test "parse multi-return with named `!Foo` as trailing element" {
|
||||
const source = "f :: () -> (s32, s64, !ParseErr) { 0; }";
|
||||
const source = "f :: () -> (i32, i64, !ParseErr) { 0; }";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), source);
|
||||
@@ -4435,7 +4435,7 @@ test "parse multi-return with named `!Foo` as trailing element" {
|
||||
}
|
||||
|
||||
test "parse error type rejected when not the trailing result element" {
|
||||
const source = "f :: () -> (!, s32) { 0; }";
|
||||
const source = "f :: () -> (!, i32) { 0; }";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), source);
|
||||
@@ -4443,7 +4443,7 @@ test "parse error type rejected when not the trailing result element" {
|
||||
}
|
||||
|
||||
test "parse error type rejected in the middle of a result list" {
|
||||
const source = "f :: () -> (s32, !, s64) { 0; }";
|
||||
const source = "f :: () -> (i32, !, i64) { 0; }";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), source);
|
||||
@@ -4679,7 +4679,7 @@ test "E1.7 return inside a closure within a cleanup body is allowed" {
|
||||
// flags, so `return` from the closure body is legal even inside `defer`.
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var parser = Parser.init(arena.allocator(), "f :: () { defer g((x: s32) -> s32 { return x; }); }");
|
||||
var parser = Parser.init(arena.allocator(), "f :: () { defer g((x: i32) -> i32 { return x; }); }");
|
||||
_ = try parser.parse();
|
||||
}
|
||||
|
||||
@@ -4812,7 +4812,7 @@ test "E0.3 or value-terminator: parse(s) or 0" {
|
||||
|
||||
test "E0.3 full failable function parses end-to-end (all E0 forms)" {
|
||||
const source =
|
||||
\\parse :: (s: string) -> (s32, !ParseErr) {
|
||||
\\parse :: (s: string) -> (i32, !ParseErr) {
|
||||
\\ onfail (e) { cleanup(s); }
|
||||
\\ v := try inner(s) or 0;
|
||||
\\ w := other(s) catch (e2) { return 0; };
|
||||
|
||||
@@ -12,8 +12,8 @@ const types = @import("types.zig");
|
||||
const Type = types.Type;
|
||||
|
||||
// the backtick raw escape must hold in BOTH classifiers. A raw
|
||||
// reserved-name type reference (`` `s2 ``) resolves to the user-declared type,
|
||||
// while a BARE `s2` stays the builtin int. Before the fix sema's
|
||||
// reserved-name type reference (`` `i2 ``) resolves to the user-declared type,
|
||||
// while a BARE `i2` stays the builtin int. Before the fix sema's
|
||||
// `resolveTypeNode` ran `Type.fromName` first and ignored `is_raw`, so the
|
||||
// editor index would show the builtin for backtick code (the
|
||||
// two-resolver divergence applied to raw types).
|
||||
@@ -23,7 +23,7 @@ test "sema: backtick raw type reference resolves to the user type; bare stays bu
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const src =
|
||||
\\`s2 :: struct { x: s64; }
|
||||
\\`i2 :: struct { x: i64; }
|
||||
\\
|
||||
;
|
||||
var parser = Parser.init(alloc, src);
|
||||
@@ -33,16 +33,16 @@ test "sema: backtick raw type reference resolves to the user type; bare stays bu
|
||||
_ = try analyzer.analyze(root);
|
||||
|
||||
// The reserved-spelled user type registered under its plain name.
|
||||
try std.testing.expect(analyzer.struct_types.contains("s2"));
|
||||
try std.testing.expect(analyzer.struct_types.contains("i2"));
|
||||
|
||||
// RAW reference (`` `s2 ``) → the user struct, NOT the 2-bit signed int.
|
||||
var raw_node = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s2", .is_raw = true } } };
|
||||
// RAW reference (`` `i2 ``) → the user struct, NOT the 2-bit signed int.
|
||||
var raw_node = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "i2", .is_raw = true } } };
|
||||
const raw_ty = analyzer.resolveTypeNode(&raw_node);
|
||||
try std.testing.expect(raw_ty == .struct_type);
|
||||
try std.testing.expectEqualStrings("s2", raw_ty.struct_type);
|
||||
try std.testing.expectEqualStrings("i2", raw_ty.struct_type);
|
||||
|
||||
// BARE `s2` → the builtin 2-bit signed int.
|
||||
var bare_node = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s2", .is_raw = false } } };
|
||||
// BARE `i2` → the builtin 2-bit signed int.
|
||||
var bare_node = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "i2", .is_raw = false } } };
|
||||
const bare_ty = analyzer.resolveTypeNode(&bare_node);
|
||||
try std.testing.expect(bare_ty == .signed);
|
||||
try std.testing.expectEqual(@as(u8, 2), bare_ty.signed);
|
||||
@@ -58,7 +58,7 @@ test "sema: a raw struct-field annotation resolves to the user type; bare stays
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const src =
|
||||
\\`u8 :: struct { y: s64; }
|
||||
\\`u8 :: struct { y: i64; }
|
||||
\\Holder :: struct { a: `u8; b: u8; }
|
||||
\\
|
||||
;
|
||||
@@ -87,12 +87,12 @@ test "sema: a raw struct-field annotation resolves to the user type; bare stays
|
||||
|
||||
// ── raw provenance through sema's COMPOUND type metadata ────────
|
||||
//
|
||||
// The direct-case fix (above) only covered a bare `` `s2 `` reference. A
|
||||
// COMPOUND raw type (`*`s2`, `?`s2`, `[N]`s2`, …) stores its inner name as a
|
||||
// The direct-case fix (above) only covered a bare `` `i2 `` reference. A
|
||||
// COMPOUND raw type (`*`i2`, `?`i2`, `[N]`i2`, …) stores its inner name as a
|
||||
// bare string on the Type's info struct; the resolver re-reads that name via
|
||||
// `resolveTypeNameStr`. Before threading `is_raw` ALONGSIDE the stored name,
|
||||
// the resolver passed `skip_builtin = false`, so the LSP index reclassified a
|
||||
// user type named `s2` as the builtin int — diverging from codegen. These
|
||||
// user type named `i2` as the builtin int — diverging from codegen. These
|
||||
// pin every compound form: the raw inner resolves to the user type (FAILS on
|
||||
// pre-fix sema), the bare inner stays the builtin (control, preserved).
|
||||
|
||||
@@ -103,15 +103,15 @@ fn symType(res: sema.SemaResult, name: []const u8) ?Type {
|
||||
return null;
|
||||
}
|
||||
|
||||
test "sema: field access through a raw `*`s2` pointer resolves the user field; bare `*s2` stays builtin" {
|
||||
test "sema: field access through a raw `*`i2` pointer resolves the user field; bare `*i2` stays builtin" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const src =
|
||||
\\`s2 :: struct { x: s64; }
|
||||
\\f :: (p: *`s2) { y := p.x; }
|
||||
\\g :: (q: *s2) { w := q.*; }
|
||||
\\`i2 :: struct { x: i64; }
|
||||
\\f :: (p: *`i2) { y := p.x; }
|
||||
\\g :: (q: *i2) { w := q.*; }
|
||||
\\
|
||||
;
|
||||
var parser = Parser.init(alloc, src);
|
||||
@@ -119,27 +119,27 @@ test "sema: field access through a raw `*`s2` pointer resolves the user field; b
|
||||
var analyzer = sema.Analyzer.init(alloc);
|
||||
const res = try analyzer.analyze(root);
|
||||
|
||||
// RAW: `p: *`s2` → field `x` on the user struct → s64. (Pre-fix: the
|
||||
// pointee `s2` reclassified to the 2-bit int, `.x` not found → unresolved.)
|
||||
// RAW: `p: *`i2` → field `x` on the user struct → i64. (Pre-fix: the
|
||||
// pointee `i2` reclassified to the 2-bit int, `.x` not found → unresolved.)
|
||||
const y = symType(res, "y") orelse return error.MissingSymbol;
|
||||
try std.testing.expect(y == .signed);
|
||||
try std.testing.expectEqual(@as(u8, 64), y.signed);
|
||||
|
||||
// CONTROL: `q: *s2` (bare) → deref yields the builtin 2-bit signed int.
|
||||
// CONTROL: `q: *i2` (bare) → deref yields the builtin 2-bit signed int.
|
||||
const w = symType(res, "w") orelse return error.MissingSymbol;
|
||||
try std.testing.expect(w == .signed);
|
||||
try std.testing.expectEqual(@as(u8, 2), w.signed);
|
||||
}
|
||||
|
||||
test "sema: unwrapping a raw `?`s2` optional resolves the user field; bare `?s2` stays builtin" {
|
||||
test "sema: unwrapping a raw `?`i2` optional resolves the user field; bare `?i2` stays builtin" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const src =
|
||||
\\`s2 :: struct { x: s64; }
|
||||
\\f :: (o: ?`s2) { if val := o { y := val.x; } }
|
||||
\\g :: (b: ?s2) { if v := b { w := v; } }
|
||||
\\`i2 :: struct { x: i64; }
|
||||
\\f :: (o: ?`i2) { if val := o { y := val.x; } }
|
||||
\\g :: (b: ?i2) { if v := b { w := v; } }
|
||||
\\
|
||||
;
|
||||
var parser = Parser.init(alloc, src);
|
||||
@@ -147,26 +147,26 @@ test "sema: unwrapping a raw `?`s2` optional resolves the user field; bare `?s2`
|
||||
var analyzer = sema.Analyzer.init(alloc);
|
||||
const res = try analyzer.analyze(root);
|
||||
|
||||
// RAW: `o: ?`s2` → `if val := o` unwraps to the user struct → `val.x` is s64.
|
||||
// (Pre-fix: the optional child `s2` reclassified to the 2-bit int.)
|
||||
// RAW: `o: ?`i2` → `if val := o` unwraps to the user struct → `val.x` is i64.
|
||||
// (Pre-fix: the optional child `i2` reclassified to the 2-bit int.)
|
||||
const y = symType(res, "y") orelse return error.MissingSymbol;
|
||||
try std.testing.expect(y == .signed);
|
||||
try std.testing.expectEqual(@as(u8, 64), y.signed);
|
||||
|
||||
// CONTROL: `b: ?s2` (bare) unwraps to the builtin 2-bit signed int.
|
||||
// CONTROL: `b: ?i2` (bare) unwraps to the builtin 2-bit signed int.
|
||||
const w = symType(res, "w") orelse return error.MissingSymbol;
|
||||
try std.testing.expect(w == .signed);
|
||||
try std.testing.expectEqual(@as(u8, 2), w.signed);
|
||||
}
|
||||
|
||||
test "sema: indexing a raw `[N]`s2` array resolves the user element; bare `[N]s2` stays builtin" {
|
||||
test "sema: indexing a raw `[N]`i2` array resolves the user element; bare `[N]i2` stays builtin" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const src =
|
||||
\\`s2 :: struct { x: s64; }
|
||||
\\f :: (a: [4]`s2, b: [4]s2) { y := a[0]; w := b[0]; }
|
||||
\\`i2 :: struct { x: i64; }
|
||||
\\f :: (a: [4]`i2, b: [4]i2) { y := a[0]; w := b[0]; }
|
||||
\\
|
||||
;
|
||||
var parser = Parser.init(alloc, src);
|
||||
@@ -174,32 +174,32 @@ test "sema: indexing a raw `[N]`s2` array resolves the user element; bare `[N]s2
|
||||
var analyzer = sema.Analyzer.init(alloc);
|
||||
const res = try analyzer.analyze(root);
|
||||
|
||||
// RAW: `a: [4]`s2` → element is the user struct. (Pre-fix: reclassified to
|
||||
// RAW: `a: [4]`i2` → element is the user struct. (Pre-fix: reclassified to
|
||||
// the 2-bit int.)
|
||||
const y = symType(res, "y") orelse return error.MissingSymbol;
|
||||
try std.testing.expect(y == .struct_type);
|
||||
try std.testing.expectEqualStrings("s2", y.struct_type);
|
||||
try std.testing.expectEqualStrings("i2", y.struct_type);
|
||||
|
||||
// CONTROL: `b: [4]s2` (bare) → element is the builtin 2-bit signed int.
|
||||
// CONTROL: `b: [4]i2` (bare) → element is the builtin 2-bit signed int.
|
||||
const w = symType(res, "w") orelse return error.MissingSymbol;
|
||||
try std.testing.expect(w == .signed);
|
||||
try std.testing.expectEqual(@as(u8, 2), w.signed);
|
||||
}
|
||||
|
||||
// Parameterized raw type (`` `s2(s64) ``). Unlike the shapes above this never
|
||||
// Parameterized raw type (`` `i2(i64) ``). Unlike the shapes above this never
|
||||
// had the divergence — instantiation resolves the base name straight against
|
||||
// `struct_types` (no builtin classifier in the path), so it passes before AND
|
||||
// after. Included as coverage that the universal model holds for the
|
||||
// parameterized form too: a `` `s2 ``-declared generic instantiates and its
|
||||
// parameterized form too: a `` `i2 ``-declared generic instantiates and its
|
||||
// field resolves.
|
||||
test "sema: a raw parameterized type `` `s2(s64) `` instantiates the user generic" {
|
||||
test "sema: a raw parameterized type `` `i2(i64) `` instantiates the user generic" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const src =
|
||||
\\`s2 :: struct ($T: Type) { items: [*]T = null; n: s64 = 0; }
|
||||
\\f :: (v: `s2(s64)) { y := v.n; }
|
||||
\\`i2 :: struct ($T: Type) { items: [*]T = null; n: i64 = 0; }
|
||||
\\f :: (v: `i2(i64)) { y := v.n; }
|
||||
\\
|
||||
;
|
||||
var parser = Parser.init(alloc, src);
|
||||
@@ -207,8 +207,8 @@ test "sema: a raw parameterized type `` `s2(s64) `` instantiates the user generi
|
||||
var analyzer = sema.Analyzer.init(alloc);
|
||||
const res = try analyzer.analyze(root);
|
||||
|
||||
// `v: `s2(s64)` instantiates the `` `s2 ``-declared generic; its concrete
|
||||
// field `n` resolves to s64 (the raw base name was not misread as a builtin).
|
||||
// `v: `i2(i64)` instantiates the `` `i2 ``-declared generic; its concrete
|
||||
// field `n` resolves to i64 (the raw base name was not misread as a builtin).
|
||||
const y = symType(res, "y") orelse return error.MissingSymbol;
|
||||
try std.testing.expect(y == .signed);
|
||||
try std.testing.expectEqual(@as(u8, 64), y.signed);
|
||||
|
||||
68
src/sema.zig
68
src/sema.zig
@@ -197,7 +197,7 @@ pub const Analyzer = struct {
|
||||
// Variadic param becomes a slice type
|
||||
const elem_name = switch (param.type_expr.data) {
|
||||
.type_expr => |te| te.name,
|
||||
// `..xs: []T` — the element is T, not a guessed s32.
|
||||
// `..xs: []T` — the element is T, not a guessed i32.
|
||||
.slice_type_expr => |st| if (st.element_type.data == .type_expr) st.element_type.data.type_expr.name else "<unresolved>",
|
||||
else => "<unresolved>",
|
||||
};
|
||||
@@ -447,7 +447,7 @@ pub const Analyzer = struct {
|
||||
return .void_type;
|
||||
}
|
||||
// type_expr or identifier — check aliases, enums, structs. A raw
|
||||
// reference (`` `s2 ``) skips the builtin classifier and resolves
|
||||
// reference (`` `i2 ``) skips the builtin classifier and resolves
|
||||
// through user-defined types only.
|
||||
if (tn.data == .type_expr or tn.data == .identifier) {
|
||||
const name = if (tn.data == .type_expr) tn.data.type_expr.name else tn.data.identifier.name;
|
||||
@@ -471,7 +471,7 @@ pub const Analyzer = struct {
|
||||
/// structs), falling back to primitive spellings. Unlike `Type.fromName`,
|
||||
/// this knows user-defined types; returns `unresolved` when it can't place
|
||||
/// the name. `skip_builtin` is the backtick raw escape — a raw
|
||||
/// reference (`` `s2 ``) bypasses the builtin/reserved classifier and
|
||||
/// reference (`` `i2 ``) bypasses the builtin/reserved classifier and
|
||||
/// resolves only through user-defined types, mirroring the codegen-side
|
||||
/// `TypeResolver.resolveNamed`. Inner names of compound shapes
|
||||
/// (pointer/slice element/pointee) are always bare, so their callers pass
|
||||
@@ -501,10 +501,10 @@ pub const Analyzer = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// The backtick raw bit of an inner type-name node (`` `s2 ``). A compound
|
||||
/// The backtick raw bit of an inner type-name node (`` `i2 ``). A compound
|
||||
/// shape (`*T`, `?T`, `[]T`, …) stores its inner name as a bare string, so
|
||||
/// this bit must travel ALONGSIDE that name — otherwise the
|
||||
/// resolver re-reads `s2` as the builtin int. Non-leaf nodes are never raw.
|
||||
/// resolver re-reads `i2` as the builtin int. Non-leaf nodes are never raw.
|
||||
fn typeExprIsRaw(node: *Node) bool {
|
||||
return switch (node.data) {
|
||||
.type_expr => |te| te.is_raw,
|
||||
@@ -516,7 +516,7 @@ pub const Analyzer = struct {
|
||||
/// When a compound shape stores the NAME of an ALREADY-resolved inner type
|
||||
/// (no syntactic node to read `is_raw` from — e.g. a for-loop element), a
|
||||
/// user nominal type must be re-resolved with `skip_builtin` so a struct/
|
||||
/// enum/union named `s2` is not reclassified as the builtin. Builtins keep
|
||||
/// enum/union named `i2` is not reclassified as the builtin. Builtins keep
|
||||
/// `false`. Harmless for non-colliding names (the registry lookup is the
|
||||
/// same either way).
|
||||
fn innerNameIsRaw(inner: Type) bool {
|
||||
@@ -768,7 +768,7 @@ pub const Analyzer = struct {
|
||||
if (self.struct_types.contains(target)) return .{ .struct_type = target };
|
||||
}
|
||||
} else if (sl.type_expr) |te| {
|
||||
// Handle parameterized struct: List(s32).{} parses as call node
|
||||
// Handle parameterized struct: List(i32).{} parses as call node
|
||||
if (te.data == .call) {
|
||||
if (self.resolveCalleeName(te.data.call)) |callee| {
|
||||
if (self.instantiateGeneric(callee, te.data.call.args)) |inst| return inst;
|
||||
@@ -1925,7 +1925,7 @@ test "sema: collect top-level declarations" {
|
||||
test "sema: function params as symbols" {
|
||||
const parser_mod = @import("parser.zig");
|
||||
|
||||
const source = "add :: (a: s32, b: s32) -> s32 { a + b; }";
|
||||
const source = "add :: (a: i32, b: i32) -> i32 { a + b; }";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
@@ -2020,7 +2020,7 @@ test "sema: enum and struct declarations" {
|
||||
test "sema: var_decl infers struct type from parameterized struct literal" {
|
||||
const parser_mod = @import("parser.zig");
|
||||
|
||||
const source = "List :: struct { len: s64; } main :: () { list := List.{}; }";
|
||||
const source = "List :: struct { len: i64; } main :: () { list := List.{}; }";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
@@ -2050,8 +2050,8 @@ test "sema: var_decl infers struct type from parameterized struct literal" {
|
||||
test "sema: var_decl infers struct type from parameterized call literal" {
|
||||
const parser_mod = @import("parser.zig");
|
||||
|
||||
// List(s32).{} — parser produces struct_literal with type_expr = call node
|
||||
const source = "List :: struct { len: s64; } main :: () { list := List(s32).{}; }";
|
||||
// List(i32).{} — parser produces struct_literal with type_expr = call node
|
||||
const source = "List :: struct { len: i64; } main :: () { list := List(i32).{}; }";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
@@ -2081,8 +2081,8 @@ test "sema: index into generic List(T).items resolves the element struct" {
|
||||
const parser_mod = @import("parser.zig");
|
||||
|
||||
const source =
|
||||
"Move :: struct { score: s64; }" ++
|
||||
"List :: struct ($T: Type) { items: [*]T = null; len: s64; }" ++
|
||||
"Move :: struct { score: i64; }" ++
|
||||
"List :: struct ($T: Type) { items: [*]T = null; len: i64; }" ++
|
||||
"main :: () { legal := List(Move).{}; m := legal.items[0]; }";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
@@ -2114,8 +2114,8 @@ test "sema: generic index resolves with pre-registered (imported) struct types"
|
||||
const alloc = arena.allocator();
|
||||
|
||||
// An "imported" module defining List + Move.
|
||||
const lib_src = "Move :: struct { score: s64; }" ++
|
||||
"List :: struct ($T: Type) { items: [*]T = null; len: s64; }";
|
||||
const lib_src = "Move :: struct { score: i64; }" ++
|
||||
"List :: struct ($T: Type) { items: [*]T = null; len: i64; }";
|
||||
var lib_parser = parser_mod.Parser.init(alloc, lib_src);
|
||||
const lib_root = try lib_parser.parse();
|
||||
var lib = Analyzer.init(alloc);
|
||||
@@ -2152,10 +2152,10 @@ test "sema: generic index resolves with realistic List/Move (methods, cross-refs
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const lib_src =
|
||||
"Square :: struct { index: s64; }" ++
|
||||
"Square :: struct { index: i64; }" ++
|
||||
"MoveFlag :: enum { none; promote_rook; }" ++
|
||||
"Move :: struct { from: Square; to: Square; flag: MoveFlag; is_capture :: (self: Move) -> bool { true; } }" ++
|
||||
"List :: struct ($T: Type) { items: [*]T = null; len: s64 = 0; cap: s64 = 0; append :: (list: *List(T), item: T) {} }";
|
||||
"List :: struct ($T: Type) { items: [*]T = null; len: i64 = 0; cap: i64 = 0; append :: (list: *List(T), item: T) {} }";
|
||||
var lib_parser = parser_mod.Parser.init(alloc, lib_src);
|
||||
const lib_root = try lib_parser.parse();
|
||||
var lib = Analyzer.init(alloc);
|
||||
@@ -2191,7 +2191,7 @@ test "sema: method-return slice + .ptr index + tagged-enum element" {
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const source =
|
||||
"Event :: enum { none; click: s64; }" ++
|
||||
"Event :: enum { none; click: i64; }" ++
|
||||
"Plat :: protocol { poll :: () -> []Event; }" ++
|
||||
"go :: (p: *Plat) { evs := p.poll(); e := evs.ptr[0]; }";
|
||||
var parser = parser_mod.Parser.init(alloc, source);
|
||||
@@ -2220,7 +2220,7 @@ test "sema: field access + index through a *Struct param" {
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const source =
|
||||
"Cell :: struct { v: s64; }" ++
|
||||
"Cell :: struct { v: i64; }" ++
|
||||
"Grid :: struct { cells: [4]Cell; }" ++
|
||||
"look :: (g: *Grid) { c := g.cells[0]; }";
|
||||
var parser = parser_mod.Parser.init(alloc, source);
|
||||
@@ -2243,8 +2243,8 @@ test "sema: for-loop captures resolve element, by-ref pointer, and range cursor"
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const source =
|
||||
"Move :: struct { flag: s64; }" ++
|
||||
"List :: struct ($T: Type) { items: [*]T = null; len: s64 = 0; }" ++
|
||||
"Move :: struct { flag: i64; }" ++
|
||||
"List :: struct ($T: Type) { items: [*]T = null; len: i64 = 0; }" ++
|
||||
"Game :: struct { legal: List(Move);" ++
|
||||
" scan :: (self: *Game) {" ++
|
||||
" for self.legal (m) { a := m.flag; }" ++
|
||||
@@ -2271,7 +2271,7 @@ test "sema: for-loop captures resolve element, by-ref pointer, and range cursor"
|
||||
// by-ref capture yields a pointer to the element.
|
||||
try std.testing.expect(p_ty != null and p_ty.? == .pointer_type);
|
||||
try std.testing.expectEqualStrings("Move", p_ty.?.pointer_type.pointee_name);
|
||||
// range cursor is s64.
|
||||
// range cursor is i64.
|
||||
try std.testing.expect(i_ty != null and i_ty.? == .signed);
|
||||
try std.testing.expect(i_ty.?.signed == 64);
|
||||
|
||||
@@ -2303,11 +2303,11 @@ test "sema: passing *T where a T value is expected is diagnosed" {
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const source =
|
||||
"Move :: struct { flag: s64; }" ++
|
||||
"take :: (m: Move) -> s64 { m.flag; }" ++
|
||||
"take_ptr :: (m: *Move) -> s64 { m.flag; }" ++
|
||||
"bad :: (p: *Move) -> s64 { take(p); }" ++ // *Move into a Move param → flagged
|
||||
"good :: (p: *Move) -> s64 { take_ptr(p); }"; // *Move into a *Move param → fine
|
||||
"Move :: struct { flag: i64; }" ++
|
||||
"take :: (m: Move) -> i64 { m.flag; }" ++
|
||||
"take_ptr :: (m: *Move) -> i64 { m.flag; }" ++
|
||||
"bad :: (p: *Move) -> i64 { take(p); }" ++ // *Move into a Move param → flagged
|
||||
"good :: (p: *Move) -> i64 { take_ptr(p); }"; // *Move into a *Move param → fine
|
||||
var parser = parser_mod.Parser.init(alloc, source);
|
||||
const root = try parser.parse();
|
||||
var an = Analyzer.init(alloc);
|
||||
@@ -2329,7 +2329,7 @@ test "sema: member references record fields, methods, and enum variants" {
|
||||
|
||||
const source =
|
||||
"Color :: enum { red; green; }" ++
|
||||
"P :: struct { x: s64; m :: (self: P) -> s64 { self.x; } }" ++
|
||||
"P :: struct { x: i64; m :: (self: P) -> i64 { self.x; } }" ++
|
||||
"use :: (p: *P) { a := p.x; b := p.m(); c := Color.red; }";
|
||||
var parser = parser_mod.Parser.init(alloc, source);
|
||||
const root = try parser.parse();
|
||||
@@ -2358,9 +2358,9 @@ test "sema: context in a callconv(.c) function reports a specific diagnostic" {
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const source =
|
||||
"Context :: struct { allocator: s64; data: s64; }" ++
|
||||
"cb :: () -> s64 callconv(.c) { context; 0; }" ++
|
||||
"ok :: () -> s64 { context; 0; }";
|
||||
"Context :: struct { allocator: i64; data: i64; }" ++
|
||||
"cb :: () -> i64 callconv(.c) { context; 0; }" ++
|
||||
"ok :: () -> i64 { context; 0; }";
|
||||
var parser = parser_mod.Parser.init(alloc, source);
|
||||
const root = try parser.parse();
|
||||
var an = Analyzer.init(alloc);
|
||||
@@ -2381,7 +2381,7 @@ test "sema: variable shadowing in same scope is allowed" {
|
||||
const parser_mod = @import("parser.zig");
|
||||
|
||||
// Two variables with the same name in the same function body — sx allows this
|
||||
const source = "main :: () { x : s64 = 1; x : f64 = 2.0; }";
|
||||
const source = "main :: () { x : i64 = 1; x : f64 = 2.0; }";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
@@ -2403,7 +2403,7 @@ test "sema: variable shadowing in same scope is allowed" {
|
||||
test "sema: ufcs_alias registers symbol" {
|
||||
const parser_mod = @import("parser.zig");
|
||||
|
||||
const source = "add :: (a: s64, b: s64) -> s64 { a + b; } main :: () { sum :: ufcs add; sum(1, 2); }";
|
||||
const source = "add :: (a: i64, b: i64) -> i64 { a + b; } main :: () { sum :: ufcs add; sum(1, 2); }";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
@@ -2436,7 +2436,7 @@ test "sema: ufcs_alias registers symbol" {
|
||||
test "sema: top-level ufcs_alias registers symbol" {
|
||||
const parser_mod = @import("parser.zig");
|
||||
|
||||
const source = "add :: (a: s64, b: s64) -> s64 { a + b; } sum :: ufcs add;";
|
||||
const source = "add :: (a: i64, b: i64) -> i64 { a + b; } sum :: ufcs add;";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
@@ -227,7 +227,7 @@ pub const Token = struct {
|
||||
tag: Tag,
|
||||
loc: Loc,
|
||||
/// True when an `.identifier` was introduced by a leading backtick
|
||||
/// (`` `s2 ``): a RAW identifier whose text excludes the backtick and which
|
||||
/// (`` `i2 ``): a RAW identifier whose text excludes the backtick and which
|
||||
/// the parser must NEVER type-classify (it bypasses the reserved-type-name
|
||||
/// rule). `loc` already spans only the un-backticked name, so `slice` returns
|
||||
/// the bare text.
|
||||
|
||||
@@ -43,10 +43,10 @@ pub const Type = union(enum) {
|
||||
unresolved,
|
||||
|
||||
/// `is_raw` records whether the inner type-name came from a backtick raw
|
||||
/// reference (`` `s2 ``) or an already-resolved user type. It is the
|
||||
/// reference (`` `i2 ``) or an already-resolved user type. It is the
|
||||
/// `skip_builtin` the resolver MUST pass when re-resolving the stored inner
|
||||
/// name — without it `resolveTypeNameStr` would reclassify a
|
||||
/// user type named `s2` as the builtin int, diverging from codegen. The
|
||||
/// user type named `i2` as the builtin int, diverging from codegen. The
|
||||
/// field is REQUIRED (no default) so a future construction site cannot
|
||||
/// silently drop the bit, the way the LSP index did for compound shapes.
|
||||
pub const SliceTypeInfo = struct {
|
||||
@@ -115,14 +115,7 @@ pub const Type = union(enum) {
|
||||
pub fn fromName(name: []const u8) ?Type {
|
||||
if (name.len == 0) return null;
|
||||
return switch (name[0]) {
|
||||
's' => {
|
||||
if (std.mem.eql(u8, name, "string")) return .string_type;
|
||||
if (name.len >= 2) {
|
||||
const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
|
||||
if (width >= 1 and width <= 64) return Type.s(width);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
's' => if (std.mem.eql(u8, name, "string")) .string_type else null,
|
||||
'u' => {
|
||||
if (std.mem.eql(u8, name, "usize")) return .usize_type;
|
||||
if (name.len >= 2) {
|
||||
@@ -133,6 +126,10 @@ pub const Type = union(enum) {
|
||||
},
|
||||
'i' => {
|
||||
if (std.mem.eql(u8, name, "isize")) return .isize_type;
|
||||
if (name.len >= 2) {
|
||||
const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
|
||||
if (width >= 1 and width <= 64) return Type.s(width);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
'b' => if (std.mem.eql(u8, name, "bool")) .boolean else null,
|
||||
@@ -181,14 +178,14 @@ pub const Type = union(enum) {
|
||||
}
|
||||
|
||||
/// Returns the canonical type name for this type, or null for complex types.
|
||||
/// Used for looking up impl methods on non-struct types (e.g., s32.eq).
|
||||
/// Used for looking up impl methods on non-struct types (e.g., i32.eq).
|
||||
pub fn toName(self: Type) ?[]const u8 {
|
||||
return switch (self) {
|
||||
.signed => |w| switch (w) {
|
||||
8 => "s8",
|
||||
16 => "s16",
|
||||
32 => "s32",
|
||||
64 => "s64",
|
||||
8 => "i8",
|
||||
16 => "i16",
|
||||
32 => "i32",
|
||||
64 => "i64",
|
||||
else => null,
|
||||
},
|
||||
.unsigned => |w| switch (w) {
|
||||
@@ -214,7 +211,7 @@ pub const Type = union(enum) {
|
||||
|
||||
pub fn fromTypeExpr(node: *Node) ?Type {
|
||||
if (node.data != .type_expr) return null;
|
||||
// A backtick raw type reference (`` `s2 ``) is the LITERAL name used as
|
||||
// A backtick raw type reference (`` `i2 ``) is the LITERAL name used as
|
||||
// a type — it must skip this builtin/reserved classifier and resolve
|
||||
// through user-defined types only, mirroring the codegen-
|
||||
// side `resolveNamed`'s `skip_builtin`. Returning null lets the sema
|
||||
@@ -272,12 +269,12 @@ pub const Type = union(enum) {
|
||||
return try allocator.dupe(u8, result);
|
||||
}
|
||||
|
||||
/// Format type name for mangling and display (e.g. "s32", "u8", "f64")
|
||||
/// Format type name for mangling and display (e.g. "i32", "u8", "f64")
|
||||
pub fn displayName(self: Type, allocator: std.mem.Allocator) ![]const u8 {
|
||||
return switch (self) {
|
||||
.signed => |w| {
|
||||
var buf: [4]u8 = undefined;
|
||||
const result = std.fmt.bufPrint(&buf, "s{d}", .{w}) catch unreachable;
|
||||
const result = std.fmt.bufPrint(&buf, "i{d}", .{w}) catch unreachable;
|
||||
return try allocator.dupe(u8, result);
|
||||
},
|
||||
.unsigned => |w| {
|
||||
|
||||
Reference in New Issue
Block a user