ffi M5.A.next.2b.fu2.C: heterogeneous pack ret + OOB diagnostic
Two follow-on fixes for follow-up #2 (generic pack-fn return). (1) `pack_arg_types` — a new type-only pack binding consulted by `inferExprType` for `<pack_name>[<int_literal>]`. The earlier `pack_arg_nodes`-via-synthesized-idents path lost the type during return-type inference because the synthesized idents ("__pack_args_0" etc.) only resolve once the mono scope is set up — but the inference runs BEFORE scope setup. Now `monomorphizePackFn` installs `pack_arg_types[<pack>] = arg_types` alongside the existing nodes/count maps, and `inferExprType` consults it directly. `foo(..$args) -> $R => args[2]` called as `foo(42, 3.2, "hello")` now correctly returns "hello" (string) — the third element- typed pick threads through inference to the mono ret_ty. (2) `diagPackIndexOOB` — focused diagnostic for `args[<lit>]` where the literal exceeds the pack arity. Pre-fix the substitution returned null and the standard slice-indexing fall-through emitted "unresolved args" — burying the real cause. Now: "pack index 2 out of bounds: 'args' has 1 element" at the index span. Tests: - `examples/160-pack-hetero-ret.sx` — generic `$R` with non- zeroth heterogeneous pick (returns "hello"). - `examples/161-pack-index-oob.sx` — call passes 1 arg but body indexes args[2]; locks in the OOB diagnostic shape. 200/200 example tests + `zig build test` green.
This commit is contained in:
25
examples/160-pack-hetero-ret.sx
Normal file
25
examples/160-pack-hetero-ret.sx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Variadic heterogeneous type packs — generic `$R` with
|
||||||
|
// heterogeneous element pick. `foo(..$args) -> $R => args[2]`
|
||||||
|
// returns the THIRD arg's value; the ret type is inferred from
|
||||||
|
// the third arg's concrete type per call shape.
|
||||||
|
//
|
||||||
|
// foo(42, 3.2, "hello") → returns "hello" (string).
|
||||||
|
//
|
||||||
|
// Exercises:
|
||||||
|
// - generic `$R` inference for non-zeroth pack indices.
|
||||||
|
// - heterogeneous mixed-type call args binding into distinct
|
||||||
|
// types per position (s64, f64, string).
|
||||||
|
// - `pack_arg_types` type-only binding for `inferExprType`
|
||||||
|
// pre-mono-scope: without it, the synthesized-ident detour
|
||||||
|
// loses the type because the scope isn't set up yet during
|
||||||
|
// return-type inference.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
foo :: (..$args) -> $R => args[2];
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
a := foo(42, 3.2, "hello");
|
||||||
|
print("{}\n", a);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
20
examples/161-pack-index-oob.sx
Normal file
20
examples/161-pack-index-oob.sx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Variadic heterogeneous type packs — out-of-bounds pack index
|
||||||
|
// is a compile-time error.
|
||||||
|
//
|
||||||
|
// `foo(..$args) -> $R => args[2]` accesses the third pack
|
||||||
|
// element. When called with fewer than 3 args, the literal index
|
||||||
|
// 2 is out of bounds for the pack's actual arity. The compiler
|
||||||
|
// detects this in `diagPackIndexOOB` and emits a focused
|
||||||
|
// diagnostic at the index span — pre-fix, the fall-through hit
|
||||||
|
// the standard slice-indexing path and produced "unresolved
|
||||||
|
// 'args'" which buried the real cause.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
foo :: (..$args) -> $R => args[2];
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
n : s64 = foo(99);
|
||||||
|
print("{}\n", n);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -170,6 +170,14 @@ pub const Lowering = struct {
|
|||||||
/// when no `args` slice is in scope (the mono path doesn't
|
/// when no `args` slice is in scope (the mono path doesn't
|
||||||
/// materialise the slice).
|
/// materialise the slice).
|
||||||
pack_param_count: ?std.StringHashMap(u32) = null,
|
pack_param_count: ?std.StringHashMap(u32) = null,
|
||||||
|
/// Type-only pack binding consulted by `inferExprType` for
|
||||||
|
/// `args[<lit>]` (parallel to `pack_arg_nodes` which carries the
|
||||||
|
/// AST substitution used at lowering time). Holds the concrete
|
||||||
|
/// call-site arg types in declaration order — same data the
|
||||||
|
/// mono's pack-param signature uses. Lets generic-`$R` return
|
||||||
|
/// inference resolve `args[i]` to the correct concrete type even
|
||||||
|
/// before the mono's scope is set up.
|
||||||
|
pack_arg_types: ?std.StringHashMap([]const TypeId) = null,
|
||||||
struct_const_map: std.StringHashMap(StructConstInfo) = std.StringHashMap(StructConstInfo).init(std.heap.page_allocator), // "Struct.CONST" → value info
|
struct_const_map: std.StringHashMap(StructConstInfo) = std.StringHashMap(StructConstInfo).init(std.heap.page_allocator), // "Struct.CONST" → value info
|
||||||
module_const_map: std.StringHashMap(ModuleConstInfo) = std.StringHashMap(ModuleConstInfo).init(std.heap.page_allocator), // module-level value constants (e.g. AF_INET :s32: 2)
|
module_const_map: std.StringHashMap(ModuleConstInfo) = std.StringHashMap(ModuleConstInfo).init(std.heap.page_allocator), // module-level value constants (e.g. AF_INET :s32: 2)
|
||||||
foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames
|
foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames
|
||||||
@@ -4238,6 +4246,14 @@ pub const Lowering = struct {
|
|||||||
if (self.packArgNodeAt(ie)) |arg_node| {
|
if (self.packArgNodeAt(ie)) |arg_node| {
|
||||||
return self.lowerExpr(arg_node);
|
return self.lowerExpr(arg_node);
|
||||||
}
|
}
|
||||||
|
// Out-of-bounds pack indexing: object IS a pack name + index
|
||||||
|
// IS a comptime int literal but exceeds the pack arity. Emit
|
||||||
|
// a focused diagnostic so the user gets "pack index 2 out of
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
const obj = self.lowerExpr(ie.object);
|
const obj = self.lowerExpr(ie.object);
|
||||||
const idx = self.lowerExpr(ie.index);
|
const idx = self.lowerExpr(ie.index);
|
||||||
// Infer element type from the object's slice/array type
|
// Infer element type from the object's slice/array type
|
||||||
@@ -4246,6 +4262,27 @@ pub const Lowering = struct {
|
|||||||
return self.builder.emit(.{ .index_get = .{ .lhs = obj, .rhs = idx } }, elem_ty);
|
return self.builder.emit(.{ .index_get = .{ .lhs = obj, .rhs = idx } }, elem_ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Detect `<pack_name>[<int_literal>]` where the literal exceeds
|
||||||
|
/// the pack arity (or is negative). Emits a diagnostic and
|
||||||
|
/// returns true; caller skips the standard indexing path and
|
||||||
|
/// returns a placeholder Ref. Returns false for non-pack bases,
|
||||||
|
/// non-literal indices, or in-range indices.
|
||||||
|
fn diagPackIndexOOB(self: *Lowering, ie: *const ast.IndexExpr) bool {
|
||||||
|
const ppc = self.pack_param_count orelse return false;
|
||||||
|
if (ie.object.data != .identifier) return false;
|
||||||
|
const pack_name = ie.object.data.identifier.name;
|
||||||
|
const n = ppc.get(pack_name) orelse return false;
|
||||||
|
if (ie.index.data != .int_literal) return false;
|
||||||
|
const raw: i64 = ie.index.data.int_literal.value;
|
||||||
|
if (raw >= 0 and @as(u32, @intCast(raw)) < n) return false;
|
||||||
|
if (self.diagnostics) |diags| {
|
||||||
|
diags.addFmt(.err, ie.index.span, "pack index {} out of bounds: '{s}' has {} element{s}", .{
|
||||||
|
raw, pack_name, n, if (n == 1) @as([]const u8, "") else @as([]const u8, "s"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the call-site arg AST node when `ie` matches
|
/// Returns the call-site arg AST node when `ie` matches
|
||||||
/// `<pack_name>[<comptime_int_literal>]` with the pack name bound
|
/// `<pack_name>[<comptime_int_literal>]` with the pack name bound
|
||||||
/// in the active `pack_arg_nodes` map and the index in range.
|
/// in the active `pack_arg_nodes` map and the index in range.
|
||||||
@@ -8171,6 +8208,7 @@ pub const Lowering = struct {
|
|||||||
const saved_target = self.target_type;
|
const saved_target = self.target_type;
|
||||||
const saved_pan = self.pack_arg_nodes;
|
const saved_pan = self.pack_arg_nodes;
|
||||||
const saved_ppc = self.pack_param_count;
|
const saved_ppc = self.pack_param_count;
|
||||||
|
const saved_pat = self.pack_arg_types;
|
||||||
const saved_iri = self.inline_return_target;
|
const saved_iri = self.inline_return_target;
|
||||||
const saved_ctx_ref = self.current_ctx_ref;
|
const saved_ctx_ref = self.current_ctx_ref;
|
||||||
self.func_defer_base = self.defer_stack.items.len;
|
self.func_defer_base = self.defer_stack.items.len;
|
||||||
@@ -8183,6 +8221,7 @@ pub const Lowering = struct {
|
|||||||
self.target_type = saved_target;
|
self.target_type = saved_target;
|
||||||
self.pack_arg_nodes = saved_pan;
|
self.pack_arg_nodes = saved_pan;
|
||||||
self.pack_param_count = saved_ppc;
|
self.pack_param_count = saved_ppc;
|
||||||
|
self.pack_arg_types = saved_pat;
|
||||||
self.inline_return_target = saved_iri;
|
self.inline_return_target = saved_iri;
|
||||||
self.current_ctx_ref = saved_ctx_ref;
|
self.current_ctx_ref = saved_ctx_ref;
|
||||||
self.builder.func = saved_func;
|
self.builder.func = saved_func;
|
||||||
@@ -8223,8 +8262,12 @@ pub const Lowering = struct {
|
|||||||
var pre_ppc = std.StringHashMap(u32).init(self.alloc);
|
var pre_ppc = std.StringHashMap(u32).init(self.alloc);
|
||||||
defer pre_ppc.deinit();
|
defer pre_ppc.deinit();
|
||||||
pre_ppc.put(pack_name, @intCast(arg_types.len)) catch return;
|
pre_ppc.put(pack_name, @intCast(arg_types.len)) catch return;
|
||||||
|
var pre_pat = std.StringHashMap([]const TypeId).init(self.alloc);
|
||||||
|
defer pre_pat.deinit();
|
||||||
|
pre_pat.put(pack_name, arg_types) catch return;
|
||||||
self.pack_arg_nodes = pre_pan;
|
self.pack_arg_nodes = pre_pan;
|
||||||
self.pack_param_count = pre_ppc;
|
self.pack_param_count = pre_ppc;
|
||||||
|
self.pack_arg_types = pre_pat;
|
||||||
|
|
||||||
const declared_is_generic_ret = blk: {
|
const declared_is_generic_ret = blk: {
|
||||||
const rt = fd.return_type orelse break :blk false;
|
const rt = fd.return_type orelse break :blk false;
|
||||||
@@ -11462,6 +11505,22 @@ pub const Lowering = struct {
|
|||||||
} });
|
} });
|
||||||
},
|
},
|
||||||
.index_expr => |ie| {
|
.index_expr => |ie| {
|
||||||
|
// Pack-arg type lookup: `<pack_name>[<int_literal>]`.
|
||||||
|
// Read directly from `pack_arg_types` — bypasses the
|
||||||
|
// synthesized-ident detour in `pack_arg_nodes` which
|
||||||
|
// would otherwise lose the type when the mono's
|
||||||
|
// scope isn't set up yet (generic-`$R` pre-inference).
|
||||||
|
if (self.pack_arg_types) |pat| {
|
||||||
|
if (ie.object.data == .identifier and ie.index.data == .int_literal) {
|
||||||
|
if (pat.get(ie.object.data.identifier.name)) |arg_tys| {
|
||||||
|
const raw: i64 = ie.index.data.int_literal.value;
|
||||||
|
if (raw >= 0) {
|
||||||
|
const i: usize = @intCast(raw);
|
||||||
|
if (i < arg_tys.len) return arg_tys[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (self.packArgNodeAt(&ie)) |arg_node| {
|
if (self.packArgNodeAt(&ie)) |arg_node| {
|
||||||
return self.inferExprType(arg_node);
|
return self.inferExprType(arg_node);
|
||||||
}
|
}
|
||||||
|
|||||||
1
tests/expected/160-pack-hetero-ret.exit
Normal file
1
tests/expected/160-pack-hetero-ret.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
tests/expected/160-pack-hetero-ret.txt
Normal file
1
tests/expected/160-pack-hetero-ret.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
hello
|
||||||
1
tests/expected/161-pack-index-oob.exit
Normal file
1
tests/expected/161-pack-index-oob.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
1
tests/expected/161-pack-index-oob.txt
Normal file
1
tests/expected/161-pack-index-oob.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/Users/agra/projects/sx/examples/161-pack-index-oob.sx:14:32: error: pack index 2 out of bounds: 'args' has 1 element
|
||||||
Reference in New Issue
Block a user