fix(ir): single-assign field store delegates to fieldLvaluePtr, completing the lvalue consolidation [F0.10]
Migrate lowerAssignment's `.field_access` target onto the shared
`fieldLvaluePtr` resolver, deleting its duplicated union / promoted /
tuple / vector / struct walk. All three lvalue field-store sites —
single-assign, address-of (lowerExprAsPtr), and multi-assign
(lowerMultiAssign) — now resolve through the one resolver, removing the
issue-0083 two-resolver divergence.
Fold vector-lane resolution into `fieldLvaluePtr` (reusing
vectorLaneIndex) so the single resolver covers struct fields, union
direct fields, promoted anonymous-struct union members, tuple elements,
and vector lanes — null only on a genuine miss, which every caller turns
into the read path's `emitFieldError` diagnostic.
`fieldLvaluePtr` now types every field GEP `*field_ty` (the convention
the single-assign path always used), not the bare field value type:
emitStore unwraps one pointer level to find the stored value's type.
The earlier lowerExprAsPtr / lowerMultiAssign walks typed the GEP with
the bare field type, so a field whose own type is a pointer-to-aggregate
(`*Pair`, a two-pointer struct) made emitStore unwrap to the aggregate
and coerceArg's closure auto-promotion store a 16-byte `{ptr,null}`
struct over the 8-byte slot, clobbering the neighbouring field.
Consolidating onto the one `*field_ty` resolver preserves single-assign
and fixes that pre-existing multi-assign / address-of clobber.
The types.zig `.unresolved` tripwire is untouched; no `.s64` / `.void` /
`.unresolved` default remains.
Regression: examples/0167-types-ptr-to-aggregate-field-store.sx (a
`*Pair` field stored via all three lvalue sites leaves the neighbour
intact) + a lowering unit test asserting the `*field_ty` GEP convention.
This commit is contained in:
@@ -1174,6 +1174,74 @@ test "lower: multi-assign to a missing struct field emits field-not-found, no co
|
||||
try std.testing.expect(found);
|
||||
}
|
||||
|
||||
test "lower: shared resolver types a pointer-typed field GEP as *field_ty, not field_ty (issue 0094 clobber)" {
|
||||
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();
|
||||
|
||||
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);
|
||||
const fields = [_]ir_mod.types.TypeInfo.StructInfo.Field{
|
||||
.{ .name = module.types.internString("p"), .ty = ptr_s64 },
|
||||
};
|
||||
_ = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("S"), .fields = &fields } });
|
||||
|
||||
// mutate :: (s: *S, q: *s64) { 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
|
||||
// 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.
|
||||
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_ty = Node{ .span = span, .data = .{ .pointer_type_expr = .{ .pointee_type = &q_pointee } } };
|
||||
|
||||
var d_init = Node{ .span = span, .data = .{ .int_literal = .{ .value = 0 } } };
|
||||
var d_decl = Node{ .span = span, .data = .{ .var_decl = .{ .name = "d", .name_span = span, .type_annotation = null, .value = &d_init } } };
|
||||
|
||||
var s_ident = Node{ .span = span, .data = .{ .identifier = .{ .name = "s" } } };
|
||||
var target0 = Node{ .span = span, .data = .{ .field_access = .{ .object = &s_ident, .field = "p" } } };
|
||||
var target1 = Node{ .span = span, .data = .{ .identifier = .{ .name = "d" } } };
|
||||
var q_rhs = Node{ .span = span, .data = .{ .identifier = .{ .name = "q" } } };
|
||||
var v1 = Node{ .span = span, .data = .{ .int_literal = .{ .value = 1 } } };
|
||||
const targets = [_]*Node{ &target0, &target1 };
|
||||
const values = [_]*Node{ &q_rhs, &v1 };
|
||||
var massign = Node{ .span = span, .data = .{ .multi_assign = .{ .targets = &targets, .values = &values } } };
|
||||
|
||||
const stmts = [_]*Node{ &d_decl, &massign };
|
||||
var body = Node{ .span = span, .data = .{ .block = .{ .stmts = &stmts } } };
|
||||
const params = [_]ast.Param{
|
||||
.{ .name = "s", .name_span = span, .type_expr = &s_ty },
|
||||
.{ .name = "q", .name_span = span, .type_expr = &q_ty },
|
||||
};
|
||||
const fd = ast.FnDecl{ .name = "mutate", .params = ¶ms, .return_type = null, .body = &body };
|
||||
|
||||
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`).
|
||||
const func = module.getFunction(FuncId.fromIndex(0));
|
||||
var found = false;
|
||||
for (func.blocks.items) |blk| {
|
||||
for (blk.insts.items) |inst| {
|
||||
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);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
try std.testing.expect(found);
|
||||
}
|
||||
|
||||
test "lower: reflectionArgIsType accepts spelled types, rejects plain values (issue 0090)" {
|
||||
const alloc = std.testing.allocator;
|
||||
var module = ir_mod.Module.init(alloc);
|
||||
|
||||
Reference in New Issue
Block a user