diff --git a/src/AstGen.zig b/src/AstGen.zig index 0078057eef..b6a7450f3a 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1349,7 +1349,10 @@ fn arrayInitExpr( } } const array_type_inst = try typeExpr(gz, scope, array_init.ast.type_expr); - _ = try gz.addUnNode(.validate_array_init_ty, array_type_inst, array_init.ast.type_expr); + _ = try gz.addPlNode(.validate_array_init_ty, node, Zir.Inst.ArrayInit{ + .ty = array_type_inst, + .init_count = @intCast(u32, array_init.ast.elements.len), + }); break :inst .{ .array = array_type_inst, .elem = .none, diff --git a/src/Module.zig b/src/Module.zig index deff4620b9..4ac2775515 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2728,6 +2728,21 @@ pub const SrcLoc = struct { }; return nodeToSpan(tree, full.ast.value_expr); }, + .node_offset_init_ty => |node_off| { + const tree = try src_loc.file_scope.getTree(gpa); + const node_tags = tree.nodes.items(.tag); + const parent_node = src_loc.declRelativeToNodeIndex(node_off); + + var buf: [2]Ast.Node.Index = undefined; + const full: Ast.full.ArrayInit = switch (node_tags[parent_node]) { + .array_init_one, .array_init_one_comma => tree.arrayInitOne(buf[0..1], parent_node), + .array_init_dot_two, .array_init_dot_two_comma => tree.arrayInitDotTwo(&buf, parent_node), + .array_init_dot, .array_init_dot_comma => tree.arrayInitDot(parent_node), + .array_init, .array_init_comma => tree.arrayInit(parent_node), + else => unreachable, + }; + return nodeToSpan(tree, full.ast.type_expr); + }, } } @@ -3048,6 +3063,9 @@ pub const LazySrcLoc = union(enum) { /// The source location points to the default value of a field. /// The Decl is determined contextually. node_offset_field_default: i32, + /// The source location points to the type of an array or struct initializer. + /// The Decl is determined contextually. + node_offset_init_ty: i32, pub const nodeOffset = if (TracedOffset.want_tracing) nodeOffsetDebug else nodeOffsetRelease; @@ -3126,6 +3144,7 @@ pub const LazySrcLoc = union(enum) { .node_offset_ptr_hostsize, .node_offset_container_tag, .node_offset_field_default, + .node_offset_init_ty, => .{ .file_scope = decl.getFileScope(), .parent_decl_node = decl.src_node, diff --git a/src/Sema.zig b/src/Sema.zig index 5a70679b8d..74c8f0b48d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3493,19 +3493,43 @@ fn validateArrayInitTy( block: *Block, inst: Zir.Inst.Index, ) CompileError!void { - const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); - const ty = try sema.resolveType(block, src, inst_data.operand); + const ty_src: LazySrcLoc = .{ .node_offset_init_ty = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.ArrayInit, inst_data.payload_index).data; + const ty = try sema.resolveType(block, ty_src, extra.ty); switch (ty.zigTypeTag()) { - .Array, .Vector => return, + .Array => { + const array_len = ty.arrayLen(); + if (extra.init_count != array_len) { + return sema.fail(block, src, "expected {d} array elements; found {d}", .{ + array_len, extra.init_count, + }); + } + return; + }, + .Vector => { + const array_len = ty.arrayLen(); + if (extra.init_count != array_len) { + return sema.fail(block, src, "expected {d} vector elements; found {d}", .{ + array_len, extra.init_count, + }); + } + return; + }, .Struct => if (ty.isTuple()) { - // TODO validate element count + const array_len = ty.arrayLen(); + if (extra.init_count > array_len) { + return sema.fail(block, src, "expected at most {d} tuple fields; found {d}", .{ + array_len, extra.init_count, + }); + } return; }, else => {}, } - return sema.failWithArrayInitNotSupported(block, src, ty); + return sema.failWithArrayInitNotSupported(block, ty_src, ty); } fn validateStructInitTy( @@ -3741,6 +3765,15 @@ fn validateStructInit( const default_val = struct_ty.structFieldDefaultValue(i); if (default_val.tag() == .unreachable_value) { + if (struct_ty.isTuple()) { + const template = "missing tuple field with index {d}"; + if (root_msg) |msg| { + try sema.errNote(block, init_src, msg, template, .{i}); + } else { + root_msg = try sema.errMsg(block, init_src, template, .{i}); + } + continue; + } const field_name = struct_ty.structFieldName(i); const template = "missing struct field: {s}"; const args = .{field_name}; @@ -3753,7 +3786,10 @@ fn validateStructInit( } const field_src = init_src; // TODO better source location - const default_field_ptr = try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(u32, i), field_src, struct_ty, true); + const default_field_ptr = if (struct_ty.isTuple()) + try sema.tupleFieldPtr(block, init_src, struct_ptr, field_src, @intCast(u32, i), true) + else + try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(u32, i), field_src, struct_ty, true); const field_ty = sema.typeOf(default_field_ptr).childType(); const init = try sema.addConstant(field_ty, default_val); try sema.storePtr2(block, init_src, default_field_ptr, init_src, init, field_src, .store); @@ -3868,6 +3904,15 @@ fn validateStructInit( const default_val = struct_ty.structFieldDefaultValue(i); if (default_val.tag() == .unreachable_value) { + if (struct_ty.isTuple()) { + const template = "missing tuple field with index {d}"; + if (root_msg) |msg| { + try sema.errNote(block, init_src, msg, template, .{i}); + } else { + root_msg = try sema.errMsg(block, init_src, template, .{i}); + } + continue; + } const field_name = struct_ty.structFieldName(i); const template = "missing struct field: {s}"; const args = .{field_name}; @@ -3911,7 +3956,10 @@ fn validateStructInit( if (field_ptr != 0) continue; const field_src = init_src; // TODO better source location - const default_field_ptr = try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(u32, i), field_src, struct_ty, true); + const default_field_ptr = if (struct_ty.isTuple()) + try sema.tupleFieldPtr(block, init_src, struct_ptr, field_src, @intCast(u32, i), true) + else + try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(u32, i), field_src, struct_ty, true); const field_ty = sema.typeOf(default_field_ptr).childType(); const init = try sema.addConstant(field_ty, field_values[i]); try sema.storePtr2(block, init_src, default_field_ptr, init_src, init, field_src, .store); @@ -3934,15 +3982,24 @@ fn zirValidateArrayInit( const array_ty = sema.typeOf(array_ptr).childType(); const array_len = array_ty.arrayLen(); - if (instrs.len != array_len) { - if (array_ty.zigTypeTag() == .Array) { - return sema.fail(block, init_src, "expected {d} array elements; found {d}", .{ - array_len, instrs.len, - }); - } else { - return sema.fail(block, init_src, "expected {d} vector elements; found {d}", .{ - array_len, instrs.len, - }); + if (instrs.len != array_len and array_ty.isTuple()) { + const struct_obj = array_ty.castTag(.tuple).?.data; + var root_msg: ?*Module.ErrorMsg = null; + for (struct_obj.values) |default_val, i| { + if (i < instrs.len) continue; + + if (default_val.tag() == .unreachable_value) { + const template = "missing tuple field with index {d}"; + if (root_msg) |msg| { + try sema.errNote(block, init_src, msg, template, .{i}); + } else { + root_msg = try sema.errMsg(block, init_src, template, .{i}); + } + } + } + + if (root_msg) |msg| { + return sema.failWithOwnedErrorMsg(block, msg); } } @@ -3995,10 +4052,17 @@ fn zirValidateArrayInit( } first_block_index = @minimum(first_block_index, block_index); - // Array has one possible value, so value is always comptime-known - if (opt_opv) |opv| { - element_vals[i] = opv; - continue; + if (array_ty.isTuple()) { + if (array_ty.structFieldValueComptime(i)) |opv| { + element_vals[i] = opv; + continue; + } + } else { + // Array has one possible value, so value is always comptime-known + if (opt_opv) |opv| { + element_vals[i] = opv; + continue; + } } // If the next instructon is a store with a comptime operand, this element @@ -14710,6 +14774,22 @@ fn finishStructInit( field_inits[i] = try sema.addConstant(struct_obj.types[i], default_val); } } + } else if (struct_ty.isTuple()) { + const struct_obj = struct_ty.castTag(.tuple).?.data; + for (struct_obj.values) |default_val, i| { + if (field_inits[i] != .none) continue; + + if (default_val.tag() == .unreachable_value) { + const template = "missing tuple field with index {d}"; + if (root_msg) |msg| { + try sema.errNote(block, init_src, msg, template, .{i}); + } else { + root_msg = try sema.errMsg(block, init_src, template, .{i}); + } + } else { + field_inits[i] = try sema.addConstant(struct_obj.types[i], default_val); + } + } } else { const struct_obj = struct_ty.castTag(.@"struct").?.data; for (struct_obj.fields.values()) |field, i| { @@ -20255,7 +20335,7 @@ fn tupleFieldVal( return tupleFieldValByIndex(sema, block, src, tuple_byval, field_index, tuple_ty); } -/// Don't forget to check for "len" before calling this. +/// Asserts that `field_name` is not "len". fn tupleFieldIndex( sema: *Sema, block: *Block, @@ -20263,8 +20343,12 @@ fn tupleFieldIndex( field_name: []const u8, field_name_src: LazySrcLoc, ) CompileError!u32 { + assert(!std.mem.eql(u8, field_name, "len")); if (std.fmt.parseUnsigned(u32, field_name, 10)) |field_index| { if (field_index < tuple_ty.structFieldCount()) return field_index; + return sema.fail(block, field_name_src, "index '{s}' out of bounds of tuple '{}'", .{ + field_name, tuple_ty.fmt(sema.mod), + }); } else |_| {} return sema.fail(block, field_name_src, "no field named '{s}' in tuple '{}'", .{ diff --git a/src/Zir.zig b/src/Zir.zig index 4540032605..ccd677df0b 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -1709,7 +1709,7 @@ pub const Inst = struct { .switch_capture_multi_ref = .switch_capture, .array_base_ptr = .un_node, .field_base_ptr = .un_node, - .validate_array_init_ty = .un_node, + .validate_array_init_ty = .pl_node, .validate_struct_init_ty = .un_node, .validate_struct_init = .pl_node, .validate_struct_init_comptime = .pl_node, @@ -3543,6 +3543,11 @@ pub const Inst = struct { line: u32, column: u32, }; + + pub const ArrayInit = struct { + ty: Ref, + init_count: u32, + }; }; pub const SpecialProng = enum { none, @"else", under }; diff --git a/src/print_zir.zig b/src/print_zir.zig index 7723446f1c..6e33154bbd 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -229,7 +229,6 @@ const Writer = struct { .switch_cond_ref, .array_base_ptr, .field_base_ptr, - .validate_array_init_ty, .validate_struct_init_ty, .make_ptr_const, .validate_deref, @@ -246,6 +245,7 @@ const Writer = struct { .bool_br_or, => try self.writeBoolBr(stream, inst), + .validate_array_init_ty => try self.writeValidateArrayInitTy(stream, inst), .array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst), .param_type => try self.writeParamType(stream, inst), .ptr_type => try self.writePtrType(stream, inst), @@ -577,6 +577,18 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } + fn writeValidateArrayInitTy( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.ArrayInit, inst_data.payload_index).data; + try self.writeInstRef(stream, extra.ty); + try stream.print(", {d}) ", .{extra.init_count}); + try self.writeSrc(stream, inst_data.src()); + } + fn writeArrayTypeSentinel( self: *Writer, stream: anytype, diff --git a/test/cases/compile_errors/tuple_init_edge_cases.zig b/test/cases/compile_errors/tuple_init_edge_cases.zig new file mode 100644 index 0000000000..32b52cdc1f --- /dev/null +++ b/test/cases/compile_errors/tuple_init_edge_cases.zig @@ -0,0 +1,44 @@ +pub export fn entry1() void { + const T = @TypeOf(.{ 123, 3 }); + var b = T{ .@"1" = 3 }; _ = b; + var c = T{ 123, 3 }; _ = c; + var d = T{}; _ = d; +} +pub export fn entry2() void { + var a: u32 = 2; + const T = @TypeOf(.{ 123, a }); + var b = T{ .@"1" = 3 }; _ = b; + var c = T{ 123, 3 }; _ = c; + var d = T{}; _ = d; +} +pub export fn entry3() void { + var a: u32 = 2; + const T = @TypeOf(.{ 123, a }); + var b = T{ .@"0" = 123 }; _ = b; +} +comptime { + var a: u32 = 2; + const T = @TypeOf(.{ 123, a }); + var b = T{ .@"0" = 123 }; _ = b; + var c = T{ 123, 2 }; _ = c; + var d = T{}; _ = d; +} +pub export fn entry4() void { + var a: u32 = 2; + const T = @TypeOf(.{ 123, a }); + var b = T{ 123, 4, 5 }; _ = b; +} +pub export fn entry5() void { + var a: u32 = 2; + const T = @TypeOf(.{ 123, a }); + var b = T{ .@"0" = 123, .@"2" = 123, .@"1" = 123 }; _ = b; +} + +// error +// backend=stage2 +// target=native +// +// :12:14: error: missing tuple field with index 1 +// :17:14: error: missing tuple field with index 1 +// :29:14: error: expected at most 2 tuple fields; found 3 +// :34:30: error: index '2' out of bounds of tuple 'tuple{comptime comptime_int = 123, u32}' diff --git a/test/cases/compile_errors/wrong_size_to_an_array_literal.zig b/test/cases/compile_errors/wrong_size_to_an_array_literal.zig index bb8b3c215c..a38d8d4d85 100644 --- a/test/cases/compile_errors/wrong_size_to_an_array_literal.zig +++ b/test/cases/compile_errors/wrong_size_to_an_array_literal.zig @@ -7,4 +7,4 @@ comptime { // backend=stage2 // target=native // -// :2:31: error: index 2 outside array of length 2 +// :2:24: error: expected 2 array elements; found 3