feat: comptime tuple-element L-values + named-tuple-literal binding (GAP 2)
Completes comptime-cursor tuple indexing (started by the read path infee86adf) and unblocks the `race` runtime synthesis. Five enablers: 1. Named-tuple-literal type inference preserves element NAMES. A `.(a = x, b = y)` passed DIRECTLY as a `$T` argument inferred to a tuple with `.names = null`, so `field_name(T, i)` reflected "" and a `make_enum` over those labels collided on the empty name. The typer now mirrors `lowerTupleLiteral`'s name capture. 2. `inferExprType` resolves a comptime-constant tuple index to the i-th field's CONCRETE type (the inference sibling of thefee86adfread path), so `tup[i].field` / methods / comparisons on it resolve. 3. Tuple-element L-VALUES by comptime index — `tup[i] = v`, `tup[i].f = v`, `@tup[i]` — lower to a typed `structGep` of field i across all four paths (`lowerAssignment`, the multi-assign store, `lowerExprAsPtr`, and address-of-index). Previously each emitted an `index_gep` with a `ptrTo(.unresolved)` element type (a tuple has no uniform element) that panicked at LLVM emit. An out-of-range comptime index now diagnoses loudly on every path instead of falling through to that panic. 4. A user generic `($X..) -> Type` call is recognized as type-shaped (`isTypeReturningCallNode`), so it can bind a `$E: Type` parameter — e.g. `make_variant(RaceResult(T), i, …)`. The static `isTypeShapedAstNode` only knew the type-returning builtins (field_type/pointee/type_of). Locked by examples/comptime/0652 (read,fee86adf) and 0653 (store + address-of + element-pointer field store).
This commit is contained in:
35
examples/comptime/0653-comptime-tuple-cursor-store.sx
Normal file
35
examples/comptime/0653-comptime-tuple-cursor-store.sx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Comptime-cursor tuple-element L-VALUES: writing a named-tuple element by a
|
||||||
|
// comptime-constant index — the store/address-of siblings of 0652's read path.
|
||||||
|
// A tuple is heterogeneous, so each element L-value is a typed `structGep` of the
|
||||||
|
// i-th field (not a uniform `index_gep`): `tup[i] = v` (direct store), a field
|
||||||
|
// store through an element pointer (`tup[i].f = v`), and `@tup[i]` (address-of).
|
||||||
|
// These are what the `race` runtime needs to register a waiter on the i-th task
|
||||||
|
// handle (`tasks[i].waiter = …`). An out-of-range comptime index is a loud
|
||||||
|
// compile error on every one of these paths (no silent `ptrTo(unresolved)` panic).
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Box :: struct ($R: Type) { value: R; }
|
||||||
|
|
||||||
|
main :: () -> i32 {
|
||||||
|
// Direct element store by literal index.
|
||||||
|
t := .(a = 1, b = 2, c = 3);
|
||||||
|
t[0] = 100;
|
||||||
|
t[2] = 300;
|
||||||
|
print("t = ({}, {}, {})\n", t.a, t.b, t.c);
|
||||||
|
|
||||||
|
// Address-of an element, write through the pointer.
|
||||||
|
p := @t[1];
|
||||||
|
p.* = 200;
|
||||||
|
print("t.b via @t[1] = {}\n", t.b);
|
||||||
|
|
||||||
|
// Field store THROUGH an element pointer — `tup[i].field = v` — the exact
|
||||||
|
// L-value shape `race` uses to register a waiter (`tasks[i].waiter = …`): the
|
||||||
|
// i-th element is a `*Box`, and `.value` writes through it to the pointee.
|
||||||
|
ba : Box(i64) = .{ value = 0 };
|
||||||
|
bb : Box(bool) = .{ value = false };
|
||||||
|
handles := .(x = @ba, y = @bb);
|
||||||
|
handles[0].value = 7;
|
||||||
|
handles[1].value = true;
|
||||||
|
print("ba.value = {}, bb.value = {}\n", ba.value, bb.value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -370,12 +370,29 @@ pub const ExprTyper = struct {
|
|||||||
}
|
}
|
||||||
var field_types = std.ArrayList(TypeId).empty;
|
var field_types = std.ArrayList(TypeId).empty;
|
||||||
defer field_types.deinit(self.l.alloc);
|
defer field_types.deinit(self.l.alloc);
|
||||||
|
// Preserve the literal's element names (the NAMED-tuple form
|
||||||
|
// `.(a = x, b = y)`) so the inferred type carries them — this is
|
||||||
|
// the type bound to a generic `$T` when a named-tuple literal is
|
||||||
|
// passed DIRECTLY as a call argument. Without it `field_name(T, i)`
|
||||||
|
// reflected the empty string and a `make_enum` over those labels
|
||||||
|
// silently collided on "" (the `race` result synthesis). Mirrors
|
||||||
|
// `lowerTupleLiteral`'s name capture so the inferred type and the
|
||||||
|
// lowered value's type agree.
|
||||||
|
var names = std.ArrayList(types.StringId).empty;
|
||||||
|
defer names.deinit(self.l.alloc);
|
||||||
|
var has_names = false;
|
||||||
for (tl.elements) |elem| {
|
for (tl.elements) |elem| {
|
||||||
field_types.append(self.l.alloc, self.l.inferExprType(elem.value)) catch unreachable;
|
field_types.append(self.l.alloc, self.l.inferExprType(elem.value)) catch unreachable;
|
||||||
|
if (elem.name) |name| {
|
||||||
|
names.append(self.l.alloc, self.l.module.types.internString(name)) catch unreachable;
|
||||||
|
has_names = true;
|
||||||
|
} else {
|
||||||
|
names.append(self.l.alloc, self.l.module.types.internString("")) catch unreachable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return self.l.module.types.intern(.{ .tuple = .{
|
return self.l.module.types.intern(.{ .tuple = .{
|
||||||
.fields = self.l.alloc.dupe(TypeId, field_types.items) catch unreachable,
|
.fields = self.l.alloc.dupe(TypeId, field_types.items) catch unreachable,
|
||||||
.names = null,
|
.names = if (has_names) self.l.alloc.dupe(types.StringId, names.items) catch unreachable else null,
|
||||||
} });
|
} });
|
||||||
},
|
},
|
||||||
.index_expr => |ie| {
|
.index_expr => |ie| {
|
||||||
@@ -400,6 +417,23 @@ pub const ExprTyper = struct {
|
|||||||
return self.l.inferExprType(arg_node);
|
return self.l.inferExprType(arg_node);
|
||||||
}
|
}
|
||||||
const obj_ty = self.l.inferExprType(ie.object);
|
const obj_ty = self.l.inferExprType(ie.object);
|
||||||
|
// Comptime-constant index into a tuple VALUE — `tup[i]` where `i`
|
||||||
|
// folds to a compile-time integer (an `inline for` cursor or a
|
||||||
|
// literal). Mirrors the lowering in `lowerIndexExpr`: the result is
|
||||||
|
// the i-th tuple field's CONCRETE type (heterogeneous elements, so
|
||||||
|
// no single runtime element type). Without this the inference path
|
||||||
|
// returned `.unresolved` for `tup[i]`, so a following `.field` /
|
||||||
|
// method / comparison on it could not resolve (the `race` runtime's
|
||||||
|
// `tasks[i].state == .ready`). A runtime index falls through to the
|
||||||
|
// generic element-type path below.
|
||||||
|
if (!obj_ty.isBuiltin() and self.l.module.types.get(obj_ty) == .tuple) {
|
||||||
|
const tinfo = self.l.module.types.get(obj_ty).tuple;
|
||||||
|
if (self.l.comptimeIndexOf(ie.index)) |ci| {
|
||||||
|
if (ci >= 0 and @as(usize, @intCast(ci)) < tinfo.fields.len) {
|
||||||
|
return tinfo.fields[@intCast(ci)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Optional-chain index `opt?.xs[i]`: the object types as an
|
// Optional-chain index `opt?.xs[i]`: the object types as an
|
||||||
// optional container (`?[N]T` / `?[]T` / `?[*]T`), so the whole
|
// optional container (`?[N]T` / `?[]T` / `?[*]T`), so the whole
|
||||||
// index expression is `?ElemType` (flattened if the element is
|
// index expression is `?ElemType` (flattened if the element is
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ pub const GenericResolver = struct {
|
|||||||
if (types_passed_explicitly) {
|
if (types_passed_explicitly) {
|
||||||
for (fd.params, 0..) |param, pi| {
|
for (fd.params, 0..) |param, pi| {
|
||||||
if (std.mem.eql(u8, param.name, tp.name)) {
|
if (std.mem.eql(u8, param.name, tp.name)) {
|
||||||
if (pi < args_ast.len and type_bridge.isTypeShapedAstNode(args_ast[pi], &self.l.module.types)) {
|
if (pi < args_ast.len and (type_bridge.isTypeShapedAstNode(args_ast[pi], &self.l.module.types) or self.l.isTypeReturningCallNode(args_ast[pi]))) {
|
||||||
const ty = self.l.resolveTypeArg(args_ast[pi]);
|
const ty = self.l.resolveTypeArg(args_ast[pi]);
|
||||||
bindings.put(tp.name, ty) catch {};
|
bindings.put(tp.name, ty) catch {};
|
||||||
found = true;
|
found = true;
|
||||||
|
|||||||
@@ -2125,6 +2125,7 @@ pub const Lowering = struct {
|
|||||||
pub const lowerComptimeGenericInstanceMethod = lower_generic.lowerComptimeGenericInstanceMethod;
|
pub const lowerComptimeGenericInstanceMethod = lower_generic.lowerComptimeGenericInstanceMethod;
|
||||||
pub const assertInstanceMapsCoincide = lower_generic.assertInstanceMapsCoincide;
|
pub const assertInstanceMapsCoincide = lower_generic.assertInstanceMapsCoincide;
|
||||||
pub const isStaticTypeArg = lower_generic.isStaticTypeArg;
|
pub const isStaticTypeArg = lower_generic.isStaticTypeArg;
|
||||||
|
pub const isTypeReturningCallNode = lower_generic.isTypeReturningCallNode;
|
||||||
pub const isStaticTypeRef = lower_generic.isStaticTypeRef;
|
pub const isStaticTypeRef = lower_generic.isStaticTypeRef;
|
||||||
pub const resolveTupleLiteralTypeArg = lower_generic.resolveTupleLiteralTypeArg;
|
pub const resolveTupleLiteralTypeArg = lower_generic.resolveTupleLiteralTypeArg;
|
||||||
pub const resolveTypeArg = lower_generic.resolveTypeArg;
|
pub const resolveTypeArg = lower_generic.resolveTypeArg;
|
||||||
|
|||||||
@@ -2593,8 +2593,30 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
|||||||
// address_of(index_expr) → emit index_gep (pointer to element) instead of index_get + addr_of
|
// address_of(index_expr) → emit index_gep (pointer to element) instead of index_get + addr_of
|
||||||
if (uop.op == .address_of and uop.operand.data == .index_expr) {
|
if (uop.op == .address_of and uop.operand.data == .index_expr) {
|
||||||
const ie = &uop.operand.data.index_expr;
|
const ie = &uop.operand.data.index_expr;
|
||||||
const idx = self.lowerExpr(ie.index);
|
|
||||||
const obj_ty = self.inferExprType(ie.object);
|
const obj_ty = self.inferExprType(ie.object);
|
||||||
|
// Comptime-constant index into a tuple VALUE — `@tup[i]`. A tuple is
|
||||||
|
// heterogeneous: the element address is a typed `structGep` of the
|
||||||
|
// i-th field, never an `index_gep` (whose `ptrTo(.unresolved)`
|
||||||
|
// element type panics at LLVM emit). Out-of-range diagnoses loudly,
|
||||||
|
// mirroring the read path.
|
||||||
|
if (!obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .tuple) {
|
||||||
|
const tinfo = self.module.types.get(obj_ty).tuple;
|
||||||
|
if (self.comptimeIndexOf(ie.index)) |ci| {
|
||||||
|
if (ci >= 0 and @as(usize, @intCast(ci)) < tinfo.fields.len) {
|
||||||
|
const fi: u32 = @intCast(ci);
|
||||||
|
const fld_ty = tinfo.fields[fi];
|
||||||
|
const base = self.getExprAlloca(ie.object) orelse self.lowerExprAsPtr(ie.object);
|
||||||
|
break :blk self.builder.structGepTyped(base, fi, self.module.types.ptrTo(fld_ty), obj_ty);
|
||||||
|
}
|
||||||
|
if (self.diagnostics) |d| {
|
||||||
|
d.addFmt(.err, ie.index.span, "tuple index {} out of bounds — tuple '{s}' has {} field{s}", .{
|
||||||
|
ci, self.formatTypeName(obj_ty), tinfo.fields.len, if (tinfo.fields.len == 1) "" else "s",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break :blk self.builder.constInt(0, .i64); // placeholder — hasErrors() aborts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const idx = self.lowerExpr(ie.index);
|
||||||
const elem_ty = self.ptrToArrayElem(obj_ty) orelse self.getElementType(obj_ty);
|
const elem_ty = self.ptrToArrayElem(obj_ty) orelse self.getElementType(obj_ty);
|
||||||
const ptr_ty = self.module.types.ptrTo(elem_ty);
|
const ptr_ty = self.module.types.ptrTo(elem_ty);
|
||||||
// For array targets, use the storage pointer (alloca for a
|
// For array targets, use the storage pointer (alloca for a
|
||||||
|
|||||||
@@ -368,6 +368,33 @@ pub fn resolveTupleLiteralTypeArg(self: *Lowering, node: *const Node) TypeId {
|
|||||||
return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True iff `node` is a call to a user-defined generic `($X..) -> Type` function
|
||||||
|
/// (e.g. `RaceResult(T)`). Such a call is type-shaped: `resolveTypeArg` resolves
|
||||||
|
/// it via `resolveTypeCallWithBindings` -> `instantiateTypeFunction`. The static
|
||||||
|
/// `isTypeShapedAstNode` only recognizes the type-returning BUILTINS
|
||||||
|
/// (`field_type`/`pointee`/`type_of`) — it has no program index — so a user
|
||||||
|
/// type-fn call in a `$E: Type` argument slot would otherwise never be seen as a
|
||||||
|
/// type and the param would fail to bind ("cannot infer generic type parameter").
|
||||||
|
/// This lets a synthesized result type flow as a type argument, e.g.
|
||||||
|
/// `make_variant(RaceResult(T), i, winner.value)` in the `race` runtime.
|
||||||
|
pub fn isTypeReturningCallNode(self: *Lowering, node: *const Node) bool {
|
||||||
|
if (node.data != .call) return false;
|
||||||
|
const cl = node.data.call;
|
||||||
|
const callee_name: []const u8 = switch (cl.callee.data) {
|
||||||
|
.identifier => |id| id.name,
|
||||||
|
.field_access => |fa| fa.field,
|
||||||
|
else => return false,
|
||||||
|
};
|
||||||
|
const resolved_name = if (self.scope) |scope| (scope.lookupFn(callee_name) orelse callee_name) else callee_name;
|
||||||
|
const fd = self.program_index.fn_ast_map.get(resolved_name) orelse return false;
|
||||||
|
// Only a GENERIC `-> Type` fn resolves through `instantiateTypeFunction`; a
|
||||||
|
// non-generic one would fall to a named-type lookup that this call shape
|
||||||
|
// can't satisfy, so gate on both (matches `resolveTypeCallWithBindings`).
|
||||||
|
if (fd.type_params.len == 0) return false;
|
||||||
|
const rt = fd.return_type orelse return false;
|
||||||
|
return rt.data == .type_expr and std.mem.eql(u8, rt.data.type_expr.name, "Type");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resolveTypeArg(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])`
|
// Pack-index access in a type-arg slot (e.g. `type_name($args[0])`
|
||||||
// or `type_eq($args[i], i64)`). Same shape as the
|
// or `type_eq($args[i], i64)`). Same shape as the
|
||||||
|
|||||||
@@ -1057,8 +1057,34 @@ pub fn lowerAssignment(self: *Lowering, asgn: *const ast.Assignment) void {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.index_expr => |ie| {
|
.index_expr => |ie| {
|
||||||
const idx = self.lowerExpr(ie.index);
|
|
||||||
const obj_ty = self.inferExprType(ie.object);
|
const obj_ty = self.inferExprType(ie.object);
|
||||||
|
// Comptime-constant store into a tuple element — `tup[i] = v`. A tuple
|
||||||
|
// is heterogeneous, so the destination is a typed `structGep` of field
|
||||||
|
// `i`, never an `index_gep` (whose `ptrTo(.unresolved)` element type
|
||||||
|
// panics at LLVM emit). Mirrors the read path in `lowerIndexExpr`; an
|
||||||
|
// out-of-range comptime index diagnoses loudly here too rather than
|
||||||
|
// falling through to that panic.
|
||||||
|
if (!obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .tuple) {
|
||||||
|
const tinfo = self.module.types.get(obj_ty).tuple;
|
||||||
|
if (self.comptimeIndexOf(ie.index)) |ci| {
|
||||||
|
if (ci >= 0 and @as(usize, @intCast(ci)) < tinfo.fields.len) {
|
||||||
|
const fi: u32 = @intCast(ci);
|
||||||
|
const fld_ty = tinfo.fields[fi];
|
||||||
|
const base = self.getExprAlloca(ie.object) orelse self.lowerExprAsPtr(ie.object);
|
||||||
|
const gep = self.builder.structGepTyped(base, fi, self.module.types.ptrTo(fld_ty), obj_ty);
|
||||||
|
const coerced = self.coerceToType(val, self.builder.getRefType(val), fld_ty);
|
||||||
|
self.storeOrCompound(gep, coerced, asgn.op, fld_ty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (self.diagnostics) |d| {
|
||||||
|
d.addFmt(.err, ie.index.span, "tuple index {} out of bounds — tuple '{s}' has {} field{s}", .{
|
||||||
|
ci, self.formatTypeName(obj_ty), tinfo.fields.len, if (tinfo.fields.len == 1) "" else "s",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return; // hasErrors() aborts before codegen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const idx = self.lowerExpr(ie.index);
|
||||||
const elem_ty = self.ptrToArrayElem(obj_ty) orelse self.getElementType(obj_ty);
|
const elem_ty = self.ptrToArrayElem(obj_ty) orelse self.getElementType(obj_ty);
|
||||||
const ptr_ty = self.module.types.ptrTo(elem_ty);
|
const ptr_ty = self.module.types.ptrTo(elem_ty);
|
||||||
// For fixed-size array assignment targets, use the alloca pointer directly
|
// For fixed-size array assignment targets, use the alloca pointer directly
|
||||||
@@ -1428,8 +1454,37 @@ pub fn lowerExprAsPtr(self: *Lowering, node: *const Node) Ref {
|
|||||||
return self.emitFieldError(obj_ty, fa.field, node.span);
|
return self.emitFieldError(obj_ty, fa.field, node.span);
|
||||||
},
|
},
|
||||||
.index_expr => |ie| {
|
.index_expr => |ie| {
|
||||||
const idx = self.lowerExpr(ie.index);
|
|
||||||
const obj_ty = self.inferExprType(ie.object);
|
const obj_ty = self.inferExprType(ie.object);
|
||||||
|
// Comptime-constant index into a tuple VALUE — the L-value sibling of
|
||||||
|
// `lowerIndexExpr`'s tuple read path. A tuple is heterogeneous, so its
|
||||||
|
// element address is a `structGep` of the i-th field (typed with that
|
||||||
|
// field's type), NOT an `index_gep` (which assumes a uniform element
|
||||||
|
// type — `getElementType(tuple)` is `.unresolved`, and an `index_gep`
|
||||||
|
// with a `ptrTo(.unresolved)` result panics at LLVM emit). Needed for
|
||||||
|
// `tasks[i].waiter = …` in the `race` runtime, where the i-th element
|
||||||
|
// is read back as a pointer to GEP into its pointee.
|
||||||
|
if (!obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .tuple) {
|
||||||
|
const tinfo = self.module.types.get(obj_ty).tuple;
|
||||||
|
if (self.comptimeIndexOf(ie.index)) |ci| {
|
||||||
|
if (ci >= 0 and @as(usize, @intCast(ci)) < tinfo.fields.len) {
|
||||||
|
const fi: u32 = @intCast(ci);
|
||||||
|
const fld_ty = tinfo.fields[fi];
|
||||||
|
const base = self.getExprAlloca(ie.object) orelse self.lowerExprAsPtr(ie.object);
|
||||||
|
return self.builder.structGepTyped(base, fi, self.module.types.ptrTo(fld_ty), obj_ty);
|
||||||
|
}
|
||||||
|
// Comptime index out of range — diagnose loudly (mirror the
|
||||||
|
// read path in `lowerIndexExpr`) rather than falling through to
|
||||||
|
// the `index_gep` below, whose `ptrTo(.unresolved)` element type
|
||||||
|
// would panic at LLVM emit with no source diagnostic.
|
||||||
|
if (self.diagnostics) |d| {
|
||||||
|
d.addFmt(.err, ie.index.span, "tuple index {} out of bounds — tuple '{s}' has {} field{s}", .{
|
||||||
|
ci, self.formatTypeName(obj_ty), tinfo.fields.len, if (tinfo.fields.len == 1) "" else "s",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return self.builder.constInt(0, .i64); // placeholder — hasErrors() aborts before codegen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const idx = self.lowerExpr(ie.index);
|
||||||
const elem_ty = self.ptrToArrayElem(obj_ty) orelse self.getElementType(obj_ty);
|
const elem_ty = self.ptrToArrayElem(obj_ty) orelse self.getElementType(obj_ty);
|
||||||
const ptr_ty = self.module.types.ptrTo(elem_ty);
|
const ptr_ty = self.module.types.ptrTo(elem_ty);
|
||||||
// For fixed-size arrays, use the alloca so GEP addresses the original memory
|
// For fixed-size arrays, use the alloca so GEP addresses the original memory
|
||||||
@@ -1693,8 +1748,37 @@ pub fn lowerMultiAssign(self: *Lowering, ma: *const ast.MultiAssign) void {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.index_expr => |ie| {
|
.index_expr => |ie| {
|
||||||
const idx = self.lowerExpr(ie.index);
|
|
||||||
const obj_ty = self.inferExprType(ie.object);
|
const obj_ty = self.inferExprType(ie.object);
|
||||||
|
// Comptime-constant direct store into a tuple element — `tup[i] = v`
|
||||||
|
// (the store sibling of the L-value tuple path above). Heterogeneous
|
||||||
|
// elements → a typed `structGep` of field `i`, never an `index_gep`
|
||||||
|
// (a uniform-element op whose `ptrTo(.unresolved)` element type would
|
||||||
|
// panic at LLVM emit).
|
||||||
|
if (!obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .tuple) {
|
||||||
|
const tinfo = self.module.types.get(obj_ty).tuple;
|
||||||
|
if (self.comptimeIndexOf(ie.index)) |ci| {
|
||||||
|
if (ci >= 0 and @as(usize, @intCast(ci)) < tinfo.fields.len) {
|
||||||
|
const fi: u32 = @intCast(ci);
|
||||||
|
const fld_ty = tinfo.fields[fi];
|
||||||
|
const base = self.getExprAlloca(ie.object) orelse self.lowerExprAsPtr(ie.object);
|
||||||
|
const gep = self.builder.structGepTyped(base, fi, self.module.types.ptrTo(fld_ty), obj_ty);
|
||||||
|
const v_ty = self.builder.getRefType(val);
|
||||||
|
const sv = if (v_ty != fld_ty and v_ty != .void and fld_ty != .void) self.coerceToType(val, v_ty, fld_ty) else val;
|
||||||
|
self.builder.store(gep, sv);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Comptime index out of range — diagnose loudly instead of
|
||||||
|
// falling through to the `index_gep` store (whose
|
||||||
|
// `ptrTo(.unresolved)` element type would panic at LLVM emit).
|
||||||
|
if (self.diagnostics) |d| {
|
||||||
|
d.addFmt(.err, ie.index.span, "tuple index {} out of bounds — tuple '{s}' has {} field{s}", .{
|
||||||
|
ci, self.formatTypeName(obj_ty), tinfo.fields.len, if (tinfo.fields.len == 1) "" else "s",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
continue; // hasErrors() aborts before codegen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const idx = self.lowerExpr(ie.index);
|
||||||
const elem_ty = self.ptrToArrayElem(obj_ty) orelse self.getElementType(obj_ty);
|
const elem_ty = self.ptrToArrayElem(obj_ty) orelse self.getElementType(obj_ty);
|
||||||
const ptr_ty = self.module.types.ptrTo(elem_ty);
|
const ptr_ty = self.module.types.ptrTo(elem_ty);
|
||||||
const val_ty = self.builder.getRefType(val);
|
const val_ty = self.builder.getRefType(val);
|
||||||
|
|||||||
Reference in New Issue
Block a user