From 82d6b8da0e2687baa2942bddf34f3ed8519c460d Mon Sep 17 00:00:00 2001 From: agra Date: Thu, 11 Jun 2026 12:15:45 +0300 Subject: [PATCH] fix(0117): pointer-to-array indexing auto-derefs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A '*[N]T' receiver in an index expression reached LLVM emission with an unresolved element type and tripped the panic sentinel — no read or write spelling worked. ptrToArrayElem on Lowering recognises the shape; the index READ path GEPs the pointee array through the pointer value and loads the element; the write / compound-assign / lvalue / addr-of-element paths and the expression typer resolve the element type through the same helper (their GEP machinery already handled a pointer base). Kept out of getElementType so slice paths don't half-accept a raw pointer base. Regression: examples/0176 (read, write, compound, element ptr + deref). --- examples/0176-types-pointer-to-array-index.sx | 21 +++++++++++++++++++ .../0176-types-pointer-to-array-index.exit | 1 + .../0176-types-pointer-to-array-index.stderr | 1 + .../0176-types-pointer-to-array-index.stdout | 3 +++ ...pointer-to-array-index-unresolved-panic.md | 10 +++++++++ src/ir/expr_typer.zig | 1 + src/ir/lower.zig | 14 +++++++++++++ src/ir/lower/expr.zig | 8 ++++++- src/ir/lower/stmt.zig | 9 ++++---- 9 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 examples/0176-types-pointer-to-array-index.sx create mode 100644 examples/expected/0176-types-pointer-to-array-index.exit create mode 100644 examples/expected/0176-types-pointer-to-array-index.stderr create mode 100644 examples/expected/0176-types-pointer-to-array-index.stdout diff --git a/examples/0176-types-pointer-to-array-index.sx b/examples/0176-types-pointer-to-array-index.sx new file mode 100644 index 0000000..ae8bef8 --- /dev/null +++ b/examples/0176-types-pointer-to-array-index.sx @@ -0,0 +1,21 @@ +// Indexing through a pointer-to-array auto-derefs: `p : *[N]T` makes +// `p[i]` GEP the pointee array and load the element, and `p[i] = v` / +// `p[i] += v` store through it — mirroring the struct-pointer auto-deref +// on field access. Writes through the pointer are visible in the +// original array and vice versa. +// +// Regression (issue 0117): this shape used to reach LLVM emission with +// an unresolved element type and panic. + +#import "modules/std.sx"; + +main :: () { + k : [4]s64 = .[11, 22, 33, 44]; + p := @k; + print("read={}\n", p[2]); + p[1] = 99; + p[3] += 1; + print("write={} {} {}\n", k[1], p[1], k[3]); + e := @p[2]; + print("elem-ptr={}\n", e.*); +} diff --git a/examples/expected/0176-types-pointer-to-array-index.exit b/examples/expected/0176-types-pointer-to-array-index.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/examples/expected/0176-types-pointer-to-array-index.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0176-types-pointer-to-array-index.stderr b/examples/expected/0176-types-pointer-to-array-index.stderr new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/0176-types-pointer-to-array-index.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0176-types-pointer-to-array-index.stdout b/examples/expected/0176-types-pointer-to-array-index.stdout new file mode 100644 index 0000000..002ffc2 --- /dev/null +++ b/examples/expected/0176-types-pointer-to-array-index.stdout @@ -0,0 +1,3 @@ +read=33 +write=99 99 45 +elem-ptr=33 diff --git a/issues/0117-pointer-to-array-index-unresolved-panic.md b/issues/0117-pointer-to-array-index-unresolved-panic.md index 6b391a7..4cf09e7 100644 --- a/issues/0117-pointer-to-array-index-unresolved-panic.md +++ b/issues/0117-pointer-to-array-index-unresolved-panic.md @@ -1,5 +1,15 @@ # 0117 — indexing through a pointer-to-array panics at LLVM emission +> **RESOLVED** (2026-06-11). `ptrToArrayElem` on Lowering recognises the +> `*[N]T` receiver; the index READ path GEPs the pointee array through the +> pointer value and loads the element; the WRITE / compound-assign / +> lvalue / addr-of-element paths and the expression typer resolve the +> element type through the same helper (their GEP machinery already +> handled a pointer base). Postfix deref (`p.*`) was always the deref +> spelling — the issue's `(*p)[2]` note was a wrong-syntax red herring. +> Regression test: examples/0176-types-pointer-to-array-index.sx +> (read, write, compound, element pointer + deref). + **Symptom.** Indexing through a `*[N]T` pointer is neither lowered nor diagnosed: the index expression reaches the LLVM emitter with the `.unresolved` type sentinel and trips the panic tripwire. diff --git a/src/ir/expr_typer.zig b/src/ir/expr_typer.zig index 25bff61..e77cd20 100644 --- a/src/ir/expr_typer.zig +++ b/src/ir/expr_typer.zig @@ -364,6 +364,7 @@ pub const ExprTyper = struct { return self.l.inferExprType(arg_node); } const obj_ty = self.l.inferExprType(ie.object); + if (self.l.ptrToArrayElem(obj_ty)) |elem| return elem; return self.l.getElementType(obj_ty); }, .slice_expr => |se| { diff --git a/src/ir/lower.zig b/src/ir/lower.zig index e721e75..ff29cf6 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -1106,6 +1106,20 @@ pub const Lowering = struct { }; } + /// The element type when `ty` is a POINTER TO AN ARRAY (`*[N]T` → T), + /// else null. Indexing auto-derefs this shape (GEP the pointee array + /// through the pointer value); kept OUT of `getElementType` so the + /// slice/subslice paths don't half-accept a raw pointer base. + pub fn ptrToArrayElem(self: *Lowering, ty: TypeId) ?TypeId { + if (ty.isBuiltin()) return null; + const info = self.module.types.get(ty); + if (info != .pointer) return null; + const pointee = info.pointer.pointee; + if (pointee.isBuiltin()) return null; + const pi = self.module.types.get(pointee); + return if (pi == .array) pi.array.element else null; + } + pub fn isFloat(ty: TypeId) bool { return ty == .f32 or ty == .f64; } diff --git a/src/ir/lower/expr.zig b/src/ir/lower/expr.zig index 1e27054..3924eed 100644 --- a/src/ir/lower/expr.zig +++ b/src/ir/lower/expr.zig @@ -1263,6 +1263,12 @@ pub fn lowerIndexExpr(self: *Lowering, ie: *const ast.IndexExpr) Ref { const idx = self.lowerExpr(ie.index); // Infer element type from the object's slice/array type const obj_ty = self.inferExprType(ie.object); + // `*[N]T` receiver auto-derefs (issue 0117): `obj` IS the pointer + // value — GEP the pointee array and load the element. + if (self.ptrToArrayElem(obj_ty)) |elem| { + const gep = self.builder.emit(.{ .index_gep = .{ .lhs = obj, .rhs = idx } }, self.module.types.ptrTo(elem)); + return self.builder.load(gep, elem); + } const elem_ty = self.getElementType(obj_ty); return self.builder.emit(.{ .index_get = .{ .lhs = obj, .rhs = idx } }, elem_ty); } @@ -1843,7 +1849,7 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref { const ie = &uop.operand.data.index_expr; const idx = self.lowerExpr(ie.index); const obj_ty = self.inferExprType(ie.object); - const elem_ty = 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); // For array targets, use the storage pointer (alloca for a // local, global_addr for a module global) so the resulting diff --git a/src/ir/lower/stmt.zig b/src/ir/lower/stmt.zig index 531021f..1c6236f 100644 --- a/src/ir/lower/stmt.zig +++ b/src/ir/lower/stmt.zig @@ -564,7 +564,8 @@ pub fn lowerAssignment(self: *Lowering, asgn: *const ast.Assignment) void { } } else if (asgn.target.data == .index_expr) { // For array[i] = val, set target_type to the element type - const elem_ty = self.getElementType(self.inferExprType(asgn.target.data.index_expr.object)); + const tgt_obj_ty = self.inferExprType(asgn.target.data.index_expr.object); + const elem_ty = self.ptrToArrayElem(tgt_obj_ty) orelse self.getElementType(tgt_obj_ty); if (elem_ty != .void) self.target_type = elem_ty; } else if (asgn.target.data == .field_access) { // For obj.field = val, set target_type to the field's type so RHS @@ -722,7 +723,7 @@ pub fn lowerAssignment(self: *Lowering, asgn: *const ast.Assignment) void { .index_expr => |ie| { const idx = self.lowerExpr(ie.index); const obj_ty = self.inferExprType(ie.object); - const elem_ty = 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); // For fixed-size array assignment targets, use the alloca pointer directly // so that the store modifies the original variable (not a loaded copy). @@ -939,7 +940,7 @@ pub fn lowerExprAsPtr(self: *Lowering, node: *const Node) Ref { .index_expr => |ie| { const idx = self.lowerExpr(ie.index); const obj_ty = self.inferExprType(ie.object); - const elem_ty = 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); // For fixed-size arrays, use the alloca so GEP addresses the original memory const is_array = !obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .array; @@ -1153,7 +1154,7 @@ pub fn lowerMultiAssign(self: *Lowering, ma: *const ast.MultiAssign) void { .index_expr => |ie| { const idx = self.lowerExpr(ie.index); const obj_ty = self.inferExprType(ie.object); - const elem_ty = 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 val_ty = self.builder.getRefType(val); const store_val = if (val_ty != elem_ty and val_ty != .void and elem_ty != .void)