diff --git a/src/Module.zig b/src/Module.zig index 9a19dcdd87..bab61730e5 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -364,7 +364,7 @@ pub const Struct = struct { /// Represents the declarations inside this struct. container: Scope.Container, - /// Offset from Decl node index, points to the struct AST node. + /// Offset from `owner_decl`, points to the struct AST node. node_offset: i32, pub const Field = struct { @@ -374,9 +374,16 @@ pub const Struct = struct { default_val: Value, }; - pub fn getFullyQualifiedName(struct_obj: *Struct, gpa: *Allocator) ![]u8 { + pub fn getFullyQualifiedName(s: *Struct, gpa: *Allocator) ![]u8 { // TODO this should return e.g. "std.fs.Dir.OpenOptions" - return gpa.dupe(u8, mem.spanZ(struct_obj.owner_decl.name)); + return gpa.dupe(u8, mem.spanZ(s.owner_decl.name)); + } + + pub fn srcLoc(s: Struct) SrcLoc { + return .{ + .container = .{ .decl = s.owner_decl }, + .lazy = .{ .node_offset = s.node_offset }, + }; } }; @@ -1580,6 +1587,7 @@ pub const SrcLoc = struct { .byte_offset, .token_offset, .node_offset, + .node_offset_back2tok, .node_offset_var_decl_ty, .node_offset_for_cond, .node_offset_builtin_call_arg0, @@ -1641,6 +1649,14 @@ pub const SrcLoc = struct { const token_starts = tree.tokens.items(.start); return token_starts[tok_index]; }, + .node_offset_back2tok => |node_off| { + const decl = src_loc.container.decl; + const node = decl.relativeToNodeIndex(node_off); + const tree = decl.container.file_scope.base.tree(); + const tok_index = tree.firstToken(node) - 2; + const token_starts = tree.tokens.items(.start); + return token_starts[tok_index]; + }, .node_offset_var_decl_ty => |node_off| { const decl = src_loc.container.decl; const node = decl.relativeToNodeIndex(node_off); @@ -1755,7 +1771,10 @@ pub const SrcLoc = struct { const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = decl.relativeToNodeIndex(node_off); - const tok_index = node_datas[node].rhs; + const tok_index = switch (node_tags[node]) { + .field_access => node_datas[node].rhs, + else => tree.firstToken(node) - 2, + }; const token_starts = tree.tokens.items(.start); return token_starts[tok_index]; }, @@ -1996,6 +2015,10 @@ pub const LazySrcLoc = union(enum) { /// from its containing Decl node AST index. /// The Decl is determined contextually. node_offset: i32, + /// The source location points to two tokens left of the first token of an AST node, + /// which is this value offset from its containing Decl node AST index. + /// The Decl is determined contextually. + node_offset_back2tok: i32, /// The source location points to a variable declaration type expression, /// found by taking this AST node index offset from the containing /// Decl AST node, which points to a variable declaration AST node. Next, navigate @@ -2034,10 +2057,10 @@ pub const LazySrcLoc = union(enum) { /// to the callee expression. /// The Decl is determined contextually. node_offset_call_func: i32, - /// The source location points to the field name of a field access expression, - /// found by taking this AST node index offset from the containing - /// Decl AST node, which points to a field access AST node. Next, navigate - /// to the field name token. + /// The payload is offset from the containing Decl AST node. + /// The source location points to the field name of: + /// * a field access expression (`a.b`), or + /// * the operand ("b" node) of a field initialization expression (`.a = b`) /// The Decl is determined contextually. node_offset_field_name: i32, /// The source location points to the pointer of a pointer deref expression, @@ -2122,6 +2145,7 @@ pub const LazySrcLoc = union(enum) { .byte_offset, .token_offset, .node_offset, + .node_offset_back2tok, .node_offset_var_decl_ty, .node_offset_for_cond, .node_offset_builtin_call_arg0, @@ -2164,6 +2188,7 @@ pub const LazySrcLoc = union(enum) { .byte_offset, .token_offset, .node_offset, + .node_offset_back2tok, .node_offset_var_decl_ty, .node_offset_for_cond, .node_offset_builtin_call_arg0, @@ -4011,13 +4036,23 @@ pub fn errNote( parent: *ErrorMsg, comptime format: []const u8, args: anytype, +) error{OutOfMemory}!void { + return mod.errNoteNonLazy(src.toSrcLoc(scope), parent, format, args); +} + +pub fn errNoteNonLazy( + mod: *Module, + src_loc: SrcLoc, + parent: *ErrorMsg, + comptime format: []const u8, + args: anytype, ) error{OutOfMemory}!void { const msg = try std.fmt.allocPrint(mod.gpa, format, args); errdefer mod.gpa.free(msg); parent.notes = try mod.gpa.realloc(parent.notes, parent.notes.len + 1); parent.notes[parent.notes.len - 1] = .{ - .src_loc = src.toSrcLoc(scope), + .src_loc = src_loc, .msg = msg, }; } diff --git a/src/Sema.zig b/src/Sema.zig index 216544e1df..0b5a21ce42 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -838,12 +838,100 @@ fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Ind const tracy = trace(@src()); defer tracy.end(); - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index); - const instrs = sema.code.extra[extra.end..][0..extra.data.body_len]; + const gpa = sema.gpa; + const mod = sema.mod; + const validate_inst = sema.code.instructions.items(.data)[inst].pl_node; + const struct_init_src = validate_inst.src(); + const validate_extra = sema.code.extraData(zir.Inst.Block, validate_inst.payload_index); + const instrs = sema.code.extra[validate_extra.end..][0..validate_extra.data.body_len]; - log.warn("TODO implement zirValidateStructInitPtr (compile errors for missing/dupe fields)", .{}); + const struct_obj: *Module.Struct = s: { + const field_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node; + const field_ptr_extra = sema.code.extraData(zir.Inst.Field, field_ptr_data.payload_index).data; + const object_ptr = try sema.resolveInst(field_ptr_extra.lhs); + break :s object_ptr.ty.elemType().castTag(.@"struct").?.data; + }; + + // Maps field index to field_ptr index of where it was already initialized. + const found_fields = try gpa.alloc(zir.Inst.Index, struct_obj.fields.entries.items.len); + defer gpa.free(found_fields); + + mem.set(zir.Inst.Index, found_fields, 0); + + for (instrs) |field_ptr| { + const field_ptr_data = sema.code.instructions.items(.data)[field_ptr].pl_node; + const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_ptr_data.src_node }; + const field_ptr_extra = sema.code.extraData(zir.Inst.Field, field_ptr_data.payload_index).data; + const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start); + const field_index = struct_obj.fields.getIndex(field_name) orelse + return sema.failWithBadFieldAccess(block, struct_obj, field_src, field_name); + if (found_fields[field_index] != 0) { + const other_field_ptr = found_fields[field_index]; + const other_field_ptr_data = sema.code.instructions.items(.data)[other_field_ptr].pl_node; + const other_field_src: LazySrcLoc = .{ .node_offset_back2tok = other_field_ptr_data.src_node }; + const msg = msg: { + const msg = try mod.errMsg(&block.base, field_src, "duplicate field", .{}); + errdefer msg.destroy(gpa); + try mod.errNote(&block.base, other_field_src, msg, "other field here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); + } + found_fields[field_index] = field_ptr; + } + + var root_msg: ?*Module.ErrorMsg = null; + + for (found_fields) |field_ptr, i| { + if (field_ptr != 0) continue; + + const field_name = struct_obj.fields.entries.items[i].key; + const template = "mising struct field: {s}"; + const args = .{field_name}; + if (root_msg) |msg| { + try mod.errNote(&block.base, struct_init_src, msg, template, args); + } else { + root_msg = try mod.errMsg(&block.base, struct_init_src, template, args); + } + } + if (root_msg) |msg| { + const fqn = try struct_obj.getFullyQualifiedName(gpa); + defer gpa.free(fqn); + try mod.errNoteNonLazy( + struct_obj.srcLoc(), + msg, + "'{s}' declared here", + .{fqn}, + ); + return mod.failWithOwnedErrorMsg(&block.base, msg); + } +} + +fn failWithBadFieldAccess( + sema: *Sema, + block: *Scope.Block, + struct_obj: *Module.Struct, + field_src: LazySrcLoc, + field_name: []const u8, +) InnerError { + const mod = sema.mod; + const gpa = sema.gpa; + + const fqn = try struct_obj.getFullyQualifiedName(gpa); + defer gpa.free(fqn); + + const msg = msg: { + const msg = try mod.errMsg( + &block.base, + field_src, + "no field named '{s}' in struct '{s}'", + .{ field_name, fqn }, + ); + errdefer msg.destroy(gpa); + try mod.errNoteNonLazy(struct_obj.srcLoc(), msg, "'{s}' declared here", .{fqn}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); } fn zirStoreToBlockPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void { @@ -4245,12 +4333,8 @@ fn analyzeStructFieldPtr( const struct_obj = elem_ty.castTag(.@"struct").?.data; - const field_index = struct_obj.fields.getIndex(field_name) orelse { - // TODO note: struct S declared here - return mod.fail(&block.base, field_name_src, "no field named '{s}' in struct '{}'", .{ - field_name, elem_ty, - }); - }; + const field_index = struct_obj.fields.getIndex(field_name) orelse + return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name); const field = struct_obj.fields.entries.items[field_index].value; const ptr_field_ty = try mod.simplePtrType(arena, field.ty, true, .One); // TODO comptime field access diff --git a/src/zir.zig b/src/zir.zig index 92d155618e..c70ef17fcd 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -660,6 +660,8 @@ pub const Inst = struct { /// Given a set of `field_ptr` instructions, assumes they are all part of a struct /// initialization expression, and emits compile errors for duplicate fields /// as well as missing fields, if applicable. + /// This instruction asserts that there is at least one field_ptr instruction, + /// because it must use one of them to find out the struct type. /// Uses the `pl_node` field. Payload is `Block`. validate_struct_init_ptr, /// A struct literal with a specified type, with no fields. diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index a80b904ae4..6efb5a5e7f 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -481,6 +481,51 @@ pub fn addCases(ctx: *TestContext) !void { \\} , ""); } + + { + var case = ctx.exeFromCompiledC("structs", .{}); + case.addError( + \\const Point = struct { x: i32, y: i32 }; + \\export fn main() c_int { + \\ var p: Point = .{ + \\ .y = 24, + \\ .x = 12, + \\ .y = 24, + \\ }; + \\ return p.y - p.x - p.x; + \\} + , &.{ + ":6:10: error: duplicate field", + ":4:10: note: other field here", + }); + case.addError( + \\const Point = struct { x: i32, y: i32 }; + \\export fn main() c_int { + \\ var p: Point = .{ + \\ .y = 24, + \\ }; + \\ return p.y - p.x - p.x; + \\} + , &.{ + ":3:21: error: mising struct field: x", + ":1:15: note: 'Point' declared here", + }); + case.addError( + \\const Point = struct { x: i32, y: i32 }; + \\export fn main() c_int { + \\ var p: Point = .{ + \\ .x = 12, + \\ .y = 24, + \\ .z = 48, + \\ }; + \\ return p.y - p.x - p.x; + \\} + , &.{ + ":6:10: error: no field named 'z' in struct 'Point'", + ":1:15: note: 'Point' declared here", + }); + } + ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { \\ unreachable;