fix(ir): missing-field multi-assign + promoted-union-member lvalue [F0.10]
Completes the issue-0094 fix. attempt-1 made single-assign and address-of diagnose a missing struct field; the stress-review found two remaining defects in that change: 1. lowerMultiAssign's `.field_access` target kept the pre-fix shape — a struct-only loop that defaulted `field_idx 0` / `field_ty .unresolved` on a miss, then built the GEP and stored unconditionally. A missing field (`p.q, y = 2, 3`) silently wrote field 0 (printed `x=2 y=3`, no diagnostic), and a valid promoted-union / tuple member at a non-zero offset corrupted field 0 instead of its own slot. 2. attempt-1's new union branch in lowerExprAsPtr resolved only DIRECT union field names, so `@v.x` on a promoted anonymous-struct member reported "field 'x' not found on type 'Vec2'" even though `v.x = 41` worked. Both lvalue-pointer sites and the multi-assign store now route through one shared resolver, `fieldLvaluePtr`, that handles struct fields, union direct fields, promoted anonymous-struct union members, and tuple elements, and returns null (no field-0 / `.unresolved` default) on a genuine miss. Each caller emits the read path's `emitFieldError` on null. This collapses the three previously-divergent field-lvalue walks into one, fixing the multi-assign missing-field corruption, the promoted-member over-rejection, and (as a side effect of correct resolution) non-zero-offset promoted-union and tuple multi-assign stores. The types.zig tripwire is untouched. Regression tests: - examples/1145 extended: multi-assign missing field (`p.r, y`) errors, exit 1. - examples/0166 (new): promoted union member written and address-of'd, including a non-zero-offset member (`@v.y`), compiles and runs. - src/ir/lower.test.zig: multi-assign missing-field field-not-found unit test.
This commit is contained in:
32
examples/0166-types-union-promoted-member-lvalue.sx
Normal file
32
examples/0166-types-union-promoted-member-lvalue.sx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Taking the address of a promoted anonymous-struct union member yields a
|
||||||
|
// pointer to that member's slot, so mutating through the pointer is visible
|
||||||
|
// through the member. The write path (`v.x = 41`) and the read path already
|
||||||
|
// resolve promoted members; the lvalue-pointer path (`@v.x`) now resolves them
|
||||||
|
// too, via the shared field-lvalue resolver.
|
||||||
|
//
|
||||||
|
// Regression (issue 0094, attempt 2): lowerExprAsPtr's union branch handled
|
||||||
|
// only DIRECT union field names, so `@v.x` on a promoted member reported
|
||||||
|
// "field 'x' not found on type 'Vec2'" even though `v.x = 41` worked. The
|
||||||
|
// over-rejection is gone, and a member that is NOT at offset 0 (`v.y`) resolves
|
||||||
|
// to its own slot — not a default field 0.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Vec2 :: union {
|
||||||
|
data: [2]s64;
|
||||||
|
struct { x: s64; y: s64; };
|
||||||
|
}
|
||||||
|
|
||||||
|
bump :: (p: *s64) {
|
||||||
|
p.* = p.* + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
v : Vec2 = ---;
|
||||||
|
v.x = 41;
|
||||||
|
v.y = 100;
|
||||||
|
bump(@v.x); // promoted member at offset 0 → 42
|
||||||
|
bump(@v.y); // promoted member at offset 8 → 101 (its own slot)
|
||||||
|
print("x={}\n", v.x);
|
||||||
|
print("y={}\n", v.y);
|
||||||
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
// Assigning to a field that does not exist on a struct produces the same
|
// Assigning to a field that does not exist on a struct produces the same
|
||||||
// `field 'X' not found on type 'Y'` diagnostic as the read path (1100), and
|
// `field 'X' not found on type 'Y'` diagnostic as the read path (1100), and
|
||||||
// exits 1 — never the `.unresolved` LLVM-emission panic.
|
// exits 1 — never the `.unresolved` LLVM-emission panic, never a silent store
|
||||||
|
// into a neighbouring field.
|
||||||
//
|
//
|
||||||
// Regression (issue 0094): the lvalue field lookup left `field_ty = .unresolved`
|
// Regression (issue 0094): the lvalue field lookup left `field_ty = .unresolved`
|
||||||
// (lowerAssignment's assignment-target path) or silently GEP'd field 0 as `.s64`
|
// (lowerAssignment's assignment-target path) or silently GEP'd field 0 as `.s64`
|
||||||
// (lowerExprAsPtr's fallback), so a missing-field store built a
|
// (lowerExprAsPtr's fallback / lowerMultiAssign's struct loop), so a missing-field
|
||||||
// pointer-to-`.unresolved` that panicked at LLVM emission. Both the
|
// store either built a pointer-to-`.unresolved` that panicked at LLVM emission or
|
||||||
// assignment-target path (`p.q`) and the nested lvalue-pointer path
|
// silently wrote field 0. All three lvalue sites now emit the field-not-found
|
||||||
// (`o.missing.a`) now emit the field-not-found diagnostic.
|
// diagnostic: the assignment-target path (`p.q`), the nested lvalue-pointer path
|
||||||
|
// (`o.missing.a`), and the multi-target store path (`p.r, y`).
|
||||||
|
|
||||||
Point :: struct { x: s64; }
|
Point :: struct { x: s64; }
|
||||||
Inner :: struct { a: s64; }
|
Inner :: struct { a: s64; }
|
||||||
@@ -20,5 +22,8 @@ main :: () -> s32 {
|
|||||||
o := Outer.{ inner = Inner.{ a = 1 } };
|
o := Outer.{ inner = Inner.{ a = 1 } };
|
||||||
o.missing.a = 5; // site 2: lowerExprAsPtr fallback
|
o.missing.a = 5; // site 2: lowerExprAsPtr fallback
|
||||||
|
|
||||||
|
y : s64 = 0;
|
||||||
|
p.r, y = 3, 4; // site 3: lowerMultiAssign field path
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
x=42
|
||||||
|
y=101
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
error: field 'q' not found on type 'Point'
|
error: field 'q' not found on type 'Point'
|
||||||
--> examples/1145-diagnostics-missing-struct-field-assign.sx:18:5
|
--> examples/1145-diagnostics-missing-struct-field-assign.sx:20:5
|
||||||
|
|
|
|
||||||
18 | p.q = 2; // site 1: lowerAssignment target path
|
20 | p.q = 2; // site 1: lowerAssignment target path
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
||||||
error: field 'missing' not found on type 'Outer'
|
error: field 'missing' not found on type 'Outer'
|
||||||
--> examples/1145-diagnostics-missing-struct-field-assign.sx:21:5
|
--> examples/1145-diagnostics-missing-struct-field-assign.sx:23:5
|
||||||
|
|
|
|
||||||
21 | o.missing.a = 5; // site 2: lowerExprAsPtr fallback
|
23 | o.missing.a = 5; // site 2: lowerExprAsPtr fallback
|
||||||
| ^^^^^^^^^
|
| ^^^^^^^^^
|
||||||
|
|
||||||
|
error: field 'r' not found on type 'Point'
|
||||||
|
--> examples/1145-diagnostics-missing-struct-field-assign.sx:26:5
|
||||||
|
|
|
||||||
|
26 | p.r, y = 3, 4; // site 3: lowerMultiAssign field path
|
||||||
|
| ^^^
|
||||||
|
|||||||
@@ -10,26 +10,36 @@
|
|||||||
> on a miss it returned `structGepTyped(obj_ptr, 0, .s64, obj_ty)` — a silent
|
> on a miss it returned `structGepTyped(obj_ptr, 0, .s64, obj_ty)` — a silent
|
||||||
> field-0/`.s64` default.
|
> field-0/`.s64` default.
|
||||||
>
|
>
|
||||||
> **Fix (`src/ir/lower.zig`):**
|
> **Fix (`src/ir/lower.zig`):** all three lvalue field-store sites — single
|
||||||
> 1. `lowerAssignment` `.field_access` target — track a `found` flag over the
|
> assignment, address-of, and multi-target assignment — route field resolution
|
||||||
> struct-field loop; on a miss, emit the read path's field-not-found
|
> through one shared helper, `fieldLvaluePtr(obj_ptr, obj_ty, field)`, which
|
||||||
> diagnostic (`emitFieldError`) and bail, never constructing
|
> resolves struct fields, union/tagged-union direct fields, promoted
|
||||||
> `ptrTo(.unresolved)`.
|
> anonymous-struct union members, and tuple elements, and returns `null` (no
|
||||||
> 2. `lowerExprAsPtr` `.field_access` — resolve union/tagged-union fields via
|
> field 0 / `.unresolved` default) when nothing matches. Each caller emits the
|
||||||
> `union_gep` (mirroring the write path; the old `.s64` fallback was silently
|
> read path's field-not-found diagnostic (`emitFieldError`) on a `null` result:
|
||||||
> standing in for union field access), then the struct-field loop, then
|
> 1. `lowerAssignment` `.field_access` target — `found`-flag bail on the struct
|
||||||
> `emitFieldError` on a genuine miss. The `.s64` sentinel is gone.
|
> loop, with union direct + promoted handled before it.
|
||||||
|
> 2. `lowerExprAsPtr` `.field_access` — delegates to `fieldLvaluePtr`, so the
|
||||||
|
> address-of path now resolves promoted union members (`@v.x`) — not only
|
||||||
|
> direct union fields — and a genuine miss errors. The `.s64` sentinel is gone.
|
||||||
|
> 3. `lowerMultiAssign` `.field_access` target — replaced its struct-only loop
|
||||||
|
> (which defaulted `field_idx 0` / `field_ty .unresolved` on a miss, silently
|
||||||
|
> storing into field 0 — `p.q, y = 2, 3` printed `x=2 y=3`) with the shared
|
||||||
|
> `fieldLvaluePtr`; a missing field now errors, and a valid promoted-union /
|
||||||
|
> tuple member at a non-zero offset stores into its own slot, not field 0.
|
||||||
>
|
>
|
||||||
> Both sites now reuse `emitFieldError` (the exact facility the read path
|
> All sites reuse `emitFieldError` (the exact facility the read path
|
||||||
> `lowerFieldAccessOnType` uses), so the read and write paths reject identically.
|
> `lowerFieldAccessOnType` uses), so the read and write paths reject identically.
|
||||||
> The `types.zig` tripwire is untouched — the fix is to never produce
|
> The `src/backend/llvm/types.zig` tripwire is untouched — the fix is to never
|
||||||
> `.unresolved` for a missing-field store.
|
> produce `.unresolved` for a missing-field store.
|
||||||
>
|
>
|
||||||
> **Regression tests:** `examples/1145-diagnostics-missing-struct-field-assign.sx`
|
> **Regression tests:** `examples/1145-diagnostics-missing-struct-field-assign.sx`
|
||||||
> (negative — both sites error, exit 1), `examples/0165-types-nested-struct-field-assign.sx`
|
> (negative — single-assign, address-of, AND multi-assign missing-field all error,
|
||||||
> (positive — nested struct field write + address-of a matched field still work),
|
> exit 1), `examples/0165-types-nested-struct-field-assign.sx` (positive — nested
|
||||||
> and a lowering unit test in `src/ir/lower.test.zig`
|
> struct field write + address-of a matched field), `examples/0166-types-union-promoted-member-lvalue.sx`
|
||||||
> ("assigning to a missing struct field emits field-not-found, no panic").
|
> (positive — promoted union member written and address-of'd, including a non-zero
|
||||||
|
> offset member), and two lowering unit tests in `src/ir/lower.test.zig`
|
||||||
|
> (single- and multi-assign missing-field field-not-found).
|
||||||
|
|
||||||
## Symptom
|
## Symptom
|
||||||
Assigning to a nonexistent struct field (`p.q = ...`) panics during LLVM emission instead of reporting a source diagnostic.
|
Assigning to a nonexistent struct field (`p.q = ...`) panics during LLVM emission instead of reporting a source diagnostic.
|
||||||
|
|||||||
@@ -1121,6 +1121,59 @@ test "lower: assigning to a missing struct field emits field-not-found, no panic
|
|||||||
try std.testing.expect(found);
|
try std.testing.expect(found);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "lower: multi-assign to a missing struct field emits field-not-found, no corruption (issue 0094)" {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
var module = ir_mod.Module.init(alloc);
|
||||||
|
defer module.deinit();
|
||||||
|
var diags = errors.DiagnosticList.init(alloc, "", "test.sx");
|
||||||
|
defer diags.deinit();
|
||||||
|
|
||||||
|
// Register `Point :: struct { x: s64; }` so the struct literal resolves.
|
||||||
|
const fields = [_]ir_mod.types.TypeInfo.StructInfo.Field{
|
||||||
|
.{ .name = module.types.internString("x"), .ty = .s64 },
|
||||||
|
};
|
||||||
|
_ = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Point"), .fields = &fields } });
|
||||||
|
|
||||||
|
const span = ast.Span{ .start = 0, .end = 0 };
|
||||||
|
|
||||||
|
// main :: () { p := Point.{ x = 1 }; y := 0; p.r, y = 3, 4; } — `r` is not a field of Point.
|
||||||
|
var x_val = Node{ .span = span, .data = .{ .int_literal = .{ .value = 1 } } };
|
||||||
|
const field_inits = [_]ast.StructFieldInit{.{ .name = "x", .value = &x_val }};
|
||||||
|
var lit = Node{ .span = span, .data = .{ .struct_literal = .{ .struct_name = "Point", .field_inits = &field_inits } } };
|
||||||
|
var decl = Node{ .span = span, .data = .{ .var_decl = .{ .name = "p", .name_span = span, .type_annotation = null, .value = &lit } } };
|
||||||
|
|
||||||
|
var y_init = Node{ .span = span, .data = .{ .int_literal = .{ .value = 0 } } };
|
||||||
|
var y_decl = Node{ .span = span, .data = .{ .var_decl = .{ .name = "y", .name_span = span, .type_annotation = null, .value = &y_init } } };
|
||||||
|
|
||||||
|
var p_ident = Node{ .span = span, .data = .{ .identifier = .{ .name = "p" } } };
|
||||||
|
var target0 = Node{ .span = span, .data = .{ .field_access = .{ .object = &p_ident, .field = "r" } } };
|
||||||
|
var target1 = Node{ .span = span, .data = .{ .identifier = .{ .name = "y" } } };
|
||||||
|
var v0 = Node{ .span = span, .data = .{ .int_literal = .{ .value = 3 } } };
|
||||||
|
var v1 = Node{ .span = span, .data = .{ .int_literal = .{ .value = 4 } } };
|
||||||
|
const targets = [_]*Node{ &target0, &target1 };
|
||||||
|
const values = [_]*Node{ &v0, &v1 };
|
||||||
|
var massign = Node{ .span = span, .data = .{ .multi_assign = .{ .targets = &targets, .values = &values } } };
|
||||||
|
|
||||||
|
const stmts = [_]*Node{ &decl, &y_decl, &massign };
|
||||||
|
var body = Node{ .span = span, .data = .{ .block = .{ .stmts = &stmts } } };
|
||||||
|
const fd = ast.FnDecl{ .name = "main", .params = &.{}, .return_type = null, .body = &body };
|
||||||
|
|
||||||
|
var lowering = Lowering.init(&module);
|
||||||
|
lowering.diagnostics = &diags;
|
||||||
|
// Pre-fix the struct-only loop defaulted field_idx 0 / field_ty .unresolved on
|
||||||
|
// a miss, silently storing into field 0 (no diagnostic); the fix resolves the
|
||||||
|
// target via the shared fieldLvaluePtr and bails with field-not-found.
|
||||||
|
lowering.lowerFunction(&fd, "main", false);
|
||||||
|
|
||||||
|
var found = false;
|
||||||
|
for (diags.items.items) |d| {
|
||||||
|
if (d.level == .err and std.mem.indexOf(u8, d.message, "field 'r' not found on type 'Point'") != null) found = true;
|
||||||
|
}
|
||||||
|
try std.testing.expect(found);
|
||||||
|
}
|
||||||
|
|
||||||
test "lower: reflectionArgIsType accepts spelled types, rejects plain values (issue 0090)" {
|
test "lower: reflectionArgIsType accepts spelled types, rejects plain values (issue 0090)" {
|
||||||
const alloc = std.testing.allocator;
|
const alloc = std.testing.allocator;
|
||||||
var module = ir_mod.Module.init(alloc);
|
var module = ir_mod.Module.init(alloc);
|
||||||
|
|||||||
154
src/ir/lower.zig
154
src/ir/lower.zig
@@ -2564,6 +2564,86 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FieldLvalue = struct { ptr: Ref, ty: TypeId };
|
||||||
|
|
||||||
|
/// Resolve `obj.field` — where `obj_ptr` already points at the aggregate —
|
||||||
|
/// to a typed pointer into the field's storage plus the field's value type.
|
||||||
|
/// Handles union direct fields, promoted anonymous-struct union members,
|
||||||
|
/// tuple elements (numeric or named), and plain struct fields. Returns null
|
||||||
|
/// when no field matches; the caller emits the field-not-found diagnostic.
|
||||||
|
/// Single source of lvalue field resolution shared by lowerExprAsPtr
|
||||||
|
/// (address-of) and lowerMultiAssign (multi-target store) so the two never
|
||||||
|
/// resolve a field to a different slot or default field 0 (issue 0094).
|
||||||
|
fn fieldLvaluePtr(self: *Lowering, obj_ptr: Ref, obj_ty: TypeId, field: []const u8) ?FieldLvalue {
|
||||||
|
if (obj_ty.isBuiltin()) return null;
|
||||||
|
const field_name_id = self.module.types.internString(field);
|
||||||
|
const type_info = self.module.types.get(obj_ty);
|
||||||
|
|
||||||
|
// Union / tagged-union: variants overlay at offset 0. A direct field is
|
||||||
|
// a union_gep; a promoted anonymous-struct member is a union_gep into
|
||||||
|
// the variant followed by a struct_gep into the member.
|
||||||
|
const union_fields: ?[]const types.TypeInfo.StructInfo.Field = switch (type_info) {
|
||||||
|
.@"union" => |u| u.fields,
|
||||||
|
.tagged_union => |u| u.fields,
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
if (union_fields) |fields| {
|
||||||
|
for (fields, 0..) |f, i| {
|
||||||
|
if (f.name == field_name_id) {
|
||||||
|
const ptr = self.builder.emit(.{ .union_gep = .{ .base = obj_ptr, .field_index = @intCast(i), .base_type = obj_ty } }, self.module.types.ptrTo(f.ty));
|
||||||
|
return .{ .ptr = ptr, .ty = f.ty };
|
||||||
|
}
|
||||||
|
if (!f.ty.isBuiltin()) {
|
||||||
|
const fi = self.module.types.get(f.ty);
|
||||||
|
if (fi == .@"struct") {
|
||||||
|
for (fi.@"struct".fields, 0..) |sf, si| {
|
||||||
|
if (sf.name == field_name_id) {
|
||||||
|
const ug = self.builder.emit(.{ .union_gep = .{ .base = obj_ptr, .field_index = @intCast(i), .base_type = obj_ty } }, self.module.types.ptrTo(f.ty));
|
||||||
|
const ptr = self.builder.structGepTyped(ug, @intCast(si), sf.ty, f.ty);
|
||||||
|
return .{ .ptr = ptr, .ty = sf.ty };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tuple element: `.0` (numeric) or `.name`.
|
||||||
|
if (type_info == .tuple) {
|
||||||
|
const tup = type_info.tuple;
|
||||||
|
var elem_idx: ?usize = null;
|
||||||
|
if (std.fmt.parseInt(usize, field, 10)) |n| {
|
||||||
|
if (n < tup.fields.len) elem_idx = n;
|
||||||
|
} else |_| {
|
||||||
|
if (tup.names) |names| {
|
||||||
|
for (names, 0..) |nm, i| {
|
||||||
|
if (nm == field_name_id and i < tup.fields.len) {
|
||||||
|
elem_idx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (elem_idx) |idx| {
|
||||||
|
const elem_ty = tup.fields[idx];
|
||||||
|
const ptr = self.builder.structGepTyped(obj_ptr, @intCast(idx), elem_ty, obj_ty);
|
||||||
|
return .{ .ptr = ptr, .ty = elem_ty };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plain struct field.
|
||||||
|
const struct_fields = self.getStructFields(obj_ty);
|
||||||
|
for (struct_fields, 0..) |f, i| {
|
||||||
|
if (f.name == field_name_id) {
|
||||||
|
const ptr = self.builder.structGepTyped(obj_ptr, @intCast(i), f.ty, obj_ty);
|
||||||
|
return .{ .ptr = ptr, .ty = f.ty };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the pointer (alloca ref) for an lvalue expression, without loading.
|
/// Get the pointer (alloca ref) for an lvalue expression, without loading.
|
||||||
fn lowerExprAsPtr(self: *Lowering, node: *const Node) Ref {
|
fn lowerExprAsPtr(self: *Lowering, node: *const Node) Ref {
|
||||||
switch (node.data) {
|
switch (node.data) {
|
||||||
@@ -2608,40 +2688,15 @@ pub const Lowering = struct {
|
|||||||
obj_ty = info.pointer.pointee;
|
obj_ty = info.pointer.pointee;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const field_name_id = self.module.types.internString(fa.field);
|
// Resolve the field lvalue (struct / union direct / promoted
|
||||||
|
// anonymous-struct member / tuple element) via the shared
|
||||||
// Union / tagged-union field address: all variants overlay at
|
// resolver so address-of and the multi-target store path never
|
||||||
// offset 0, so the lvalue pointer is a union_gep — mirrors the
|
// disagree on the slot. No match → emit the read path's
|
||||||
// write path (lowerAssignment) so the lvalue-pointer and the store
|
// field-not-found diagnostic (lowerFieldAccessOnType →
|
||||||
// resolve the same field index. A non-struct aggregate would
|
// emitFieldError) instead of silently GEPing field 0 as .s64;
|
||||||
// otherwise miss the struct-field loop below and fall through.
|
// that bogus pointer reaches LLVM emission as ptrTo(.unresolved)
|
||||||
if (!obj_ty.isBuiltin()) {
|
// and panics (issue 0094).
|
||||||
const type_info = self.module.types.get(obj_ty);
|
if (self.fieldLvaluePtr(obj_ptr, obj_ty, fa.field)) |r| return r.ptr;
|
||||||
const union_fields: ?[]const types.TypeInfo.StructInfo.Field = switch (type_info) {
|
|
||||||
.@"union" => |u| u.fields,
|
|
||||||
.tagged_union => |u| u.fields,
|
|
||||||
else => null,
|
|
||||||
};
|
|
||||||
if (union_fields) |fields| {
|
|
||||||
for (fields, 0..) |f, i| {
|
|
||||||
if (f.name == field_name_id) {
|
|
||||||
return self.builder.emit(.{ .union_gep = .{ .base = obj_ptr, .field_index = @intCast(i), .base_type = obj_ty } }, self.module.types.ptrTo(f.ty));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const struct_fields = self.getStructFields(obj_ty);
|
|
||||||
for (struct_fields, 0..) |f, i| {
|
|
||||||
if (f.name == field_name_id) {
|
|
||||||
return self.builder.structGepTyped(obj_ptr, @intCast(i), f.ty, obj_ty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// No struct/union field matches — emit the read path's
|
|
||||||
// field-not-found diagnostic (lowerFieldAccessOnType → emitFieldError)
|
|
||||||
// instead of silently GEPing field 0 as .s64. That bogus pointer
|
|
||||||
// mislowers the lvalue and reaches LLVM emission as
|
|
||||||
// ptrTo(.unresolved), panicking (issue 0094).
|
|
||||||
return self.emitFieldError(obj_ty, fa.field, node.span);
|
return self.emitFieldError(obj_ty, fa.field, node.span);
|
||||||
},
|
},
|
||||||
.index_expr => |ie| {
|
.index_expr => |ie| {
|
||||||
@@ -8776,24 +8831,21 @@ pub const Lowering = struct {
|
|||||||
.field_access => |fa| {
|
.field_access => |fa| {
|
||||||
const obj_ptr = self.lowerExprAsPtr(fa.object);
|
const obj_ptr = self.lowerExprAsPtr(fa.object);
|
||||||
const obj_ty = self.inferExprType(fa.object);
|
const obj_ty = self.inferExprType(fa.object);
|
||||||
const field_name_id = self.module.types.internString(fa.field);
|
// Resolve the target field via the shared lvalue resolver —
|
||||||
const struct_fields = self.getStructFields(obj_ty);
|
// the same one address-of uses — so a missing field emits a
|
||||||
var field_idx: u32 = 0;
|
// diagnostic instead of defaulting to field 0 / field_ty
|
||||||
var field_ty: TypeId = .unresolved;
|
// .unresolved, which silently corrupted a neighbouring field
|
||||||
for (struct_fields, 0..) |f, fi| {
|
// (or panicked at LLVM emission) (issue 0094).
|
||||||
if (f.name == field_name_id) {
|
if (self.fieldLvaluePtr(obj_ptr, obj_ty, fa.field)) |r| {
|
||||||
field_idx = @intCast(fi);
|
const val_ty = self.builder.getRefType(val);
|
||||||
field_ty = f.ty;
|
const store_val = if (val_ty != r.ty and val_ty != .void and r.ty != .void)
|
||||||
break;
|
self.coerceToType(val, val_ty, r.ty)
|
||||||
}
|
else
|
||||||
|
val;
|
||||||
|
self.builder.store(r.ptr, store_val);
|
||||||
|
} else {
|
||||||
|
_ = self.emitFieldError(obj_ty, fa.field, target.span);
|
||||||
}
|
}
|
||||||
const gep = self.builder.structGepTyped(obj_ptr, field_idx, field_ty, obj_ty);
|
|
||||||
const val_ty = self.builder.getRefType(val);
|
|
||||||
const store_val = if (val_ty != field_ty and val_ty != .void and field_ty != .void)
|
|
||||||
self.coerceToType(val, val_ty, field_ty)
|
|
||||||
else
|
|
||||||
val;
|
|
||||||
self.builder.store(gep, store_val);
|
|
||||||
},
|
},
|
||||||
.deref_expr => |de| {
|
.deref_expr => |de| {
|
||||||
const ptr = self.lowerExpr(de.operand);
|
const ptr = self.lowerExpr(de.operand);
|
||||||
|
|||||||
Reference in New Issue
Block a user