fix(ir): missing struct field assignment errors cleanly, no LLVM panic [F0.10]
Assigning to a nonexistent struct field (`p.q = 2` where Point has no `q`) aborted the compiler with the `.unresolved` LLVM tripwire instead of a source diagnostic (issue 0094). The lvalue field lookup never diagnosed a miss: - `lowerAssignment`'s `.field_access` target left `field_ty = .unresolved` when no struct field matched, then built `ptrTo(field_ty)` and stored — so a pointer-to-`.unresolved` reached LLVM emission and tripped the panic. - `lowerExprAsPtr`'s `.field_access` fallback returned `structGepTyped(obj_ptr, 0, .s64, obj_ty)` on a miss — a silent field-0/`.s64` default that mislowered the lvalue. Both sites now reuse the read path's `emitFieldError` (the exact facility `lowerFieldAccessOnType` uses), so read and write reject identically with `field 'q' not found on type 'Point'`. `lowerExprAsPtr` also resolves union/tagged-union fields via `union_gep` (the old `.s64` fallback was silently standing in for union field access — e.g. `u.a[0] = v`), so that path is fixed, not just made loud. The `types.zig` tripwire is untouched: the fix is to never produce `.unresolved` for a missing-field store. Regression tests: - examples/1145-diagnostics-missing-struct-field-assign.sx — negative, both sites error, exit 1. - examples/0165-types-nested-struct-field-assign.sx — positive, nested struct field write + address-of a matched field still work. - src/ir/lower.test.zig — lowering unit test asserting the field-not-found diagnostic for a missing-field assignment.
This commit is contained in:
@@ -2472,13 +2472,25 @@ pub const Lowering = struct {
|
||||
const struct_fields = self.getStructFields(obj_ty);
|
||||
var field_idx: u32 = 0;
|
||||
var field_ty: TypeId = .unresolved;
|
||||
var found = false;
|
||||
for (struct_fields, 0..) |f, i| {
|
||||
if (f.name == field_name_id) {
|
||||
field_idx = @intCast(i);
|
||||
field_ty = f.ty;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// No struct field matches the assignment target. Emit the
|
||||
// same field-not-found diagnostic the read path uses
|
||||
// (lowerFieldAccessOnType → emitFieldError) and bail; building
|
||||
// ptrTo(field_ty) with field_ty = .unresolved would otherwise
|
||||
// store through a pointer-to-.unresolved that panics at LLVM
|
||||
// emission (issue 0094).
|
||||
_ = self.emitFieldError(obj_ty, fa.field, asgn.target.span);
|
||||
return;
|
||||
}
|
||||
// Wrap in ptrTo so the store handler sees *field_ty (consistent
|
||||
// with index_gep which uses ptrTo(elem_ty)). Without this, a
|
||||
// [*]BigNode field makes the store handler extract BigNode as the
|
||||
@@ -2596,14 +2608,41 @@ pub const Lowering = struct {
|
||||
obj_ty = info.pointer.pointee;
|
||||
}
|
||||
}
|
||||
const struct_fields = self.getStructFields(obj_ty);
|
||||
const field_name_id = self.module.types.internString(fa.field);
|
||||
|
||||
// Union / tagged-union field address: all variants overlay at
|
||||
// offset 0, so the lvalue pointer is a union_gep — mirrors the
|
||||
// write path (lowerAssignment) so the lvalue-pointer and the store
|
||||
// resolve the same field index. A non-struct aggregate would
|
||||
// otherwise miss the struct-field loop below and fall through.
|
||||
if (!obj_ty.isBuiltin()) {
|
||||
const type_info = self.module.types.get(obj_ty);
|
||||
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);
|
||||
}
|
||||
}
|
||||
return self.builder.structGepTyped(obj_ptr, 0, .s64, 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);
|
||||
},
|
||||
.index_expr => |ie| {
|
||||
const idx = self.lowerExpr(ie.index);
|
||||
|
||||
Reference in New Issue
Block a user