diff --git a/src/AstGen.zig b/src/AstGen.zig index 7d567b223d..8e6721d7bc 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -577,6 +577,12 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); + const prev_anon_name_strategy = gz.anon_name_strategy; + defer gz.anon_name_strategy = prev_anon_name_strategy; + if (!nodeUsesAnonNameStrategy(tree, node)) { + gz.anon_name_strategy = .anon; + } + switch (node_tags[node]) { .root => unreachable, // Top-level declaration. .@"usingnamespace" => unreachable, // Top-level declaration. @@ -9344,6 +9350,32 @@ fn nodeImpliesComptimeOnly(tree: *const Ast, start_node: Ast.Node.Index) bool { } } +/// Returns `true` if the node uses `gz.anon_name_strategy`. +fn nodeUsesAnonNameStrategy(tree: *const Ast, node: Ast.Node.Index) bool { + const node_tags = tree.nodes.items(.tag); + switch (node_tags[node]) { + .container_decl, + .container_decl_trailing, + .container_decl_two, + .container_decl_two_trailing, + .container_decl_arg, + .container_decl_arg_trailing, + .tagged_union, + .tagged_union_trailing, + .tagged_union_two, + .tagged_union_two_trailing, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + => return true, + .builtin_call_two, .builtin_call_two_comma, .builtin_call, .builtin_call_comma => { + const builtin_token = tree.nodes.items(.main_token)[node]; + const builtin_name = tree.tokenSlice(builtin_token); + return std.mem.eql(u8, builtin_name, "@Type"); + }, + else => return false, + } +} + /// Applies `rl` semantics to `result`. Expressions which do not do their own handling of /// result locations must call this function on their result. /// As an example, if the `ResultLoc` is `ptr`, it will write the result to the pointer. diff --git a/src/Sema.zig b/src/Sema.zig index 932a379232..a36e1d9a02 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8180,7 +8180,7 @@ fn analyzeParameter( if (param.is_comptime and !Type.fnCallingConventionAllowsZigTypes(cc)) { return sema.fail(block, param_src, "comptime parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)}); } - if (this_generic and !Type.fnCallingConventionAllowsZigTypes(cc)) { + if (this_generic and !sema.no_partial_func_ty and !Type.fnCallingConventionAllowsZigTypes(cc)) { return sema.fail(block, param_src, "generic parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)}); } if (!param.ty.isValidParamType()) { @@ -8196,7 +8196,7 @@ fn analyzeParameter( }; return sema.failWithOwnedErrorMsg(msg); } - if (!Type.fnCallingConventionAllowsZigTypes(cc) and !try sema.validateExternType(block, param_src, param.ty, .param_ty)) { + if (!this_generic and !Type.fnCallingConventionAllowsZigTypes(cc) and !try sema.validateExternType(block, param_src, param.ty, .param_ty)) { const msg = msg: { const msg = try sema.errMsg(block, param_src, "parameter of type '{}' not allowed in function with calling convention '{s}'", .{ param.ty.fmt(sema.mod), @tagName(cc), @@ -8277,8 +8277,21 @@ fn zirParam( else => |e| return e, } }; - const is_comptime = comptime_syntax or - try sema.typeRequiresComptime(param_ty); + const is_comptime = sema.typeRequiresComptime(param_ty) catch |err| switch (err) { + error.GenericPoison => { + // The type is not available until the generic instantiation. + // We result the param instruction with a poison value and + // insert an anytype parameter. + try block.params.append(sema.gpa, .{ + .ty = Type.initTag(.generic_poison), + .is_comptime = comptime_syntax, + .name = param_name, + }); + try sema.inst_map.putNoClobber(sema.gpa, inst, .generic_poison); + return; + }, + else => |e| return e, + } or comptime_syntax; if (sema.inst_map.get(inst)) |arg| { if (is_comptime) { // We have a comptime value for this parameter so it should be elided from the @@ -15966,8 +15979,8 @@ fn zirStructInit( const first_item = sema.code.extraData(Zir.Inst.StructInit.Item, extra.end).data; const first_field_type_data = zir_datas[first_item.field_type].pl_node; const first_field_type_extra = sema.code.extraData(Zir.Inst.FieldType, first_field_type_data.payload_index).data; - const unresolved_struct_type = try sema.resolveType(block, src, first_field_type_extra.container_type); - const resolved_ty = try sema.resolveTypeFields(block, src, unresolved_struct_type); + const resolved_ty = try sema.resolveType(block, src, first_field_type_extra.container_type); + try sema.resolveTypeLayout(block, src, resolved_ty); if (resolved_ty.zigTypeTag() == .Struct) { // This logic must be synchronized with that in `zirStructInitEmpty`. @@ -17017,14 +17030,14 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in const payload_val = union_val.val.optionalValue() orelse return sema.addType(Type.initTag(.anyerror)); const slice_val = payload_val.castTag(.slice).?.data; - const decl_index = slice_val.ptr.pointerDecl().?; - try sema.ensureDeclAnalyzed(decl_index); - const decl = mod.declPtr(decl_index); - const array_val: []Value = if (decl.val.castTag(.aggregate)) |some| some.data else &.{}; + const len = try sema.usizeCast(block, src, slice_val.len.toUnsignedInt(mod.getTarget())); var names: Module.ErrorSet.NameMap = .{}; - try names.ensureUnusedCapacity(sema.arena, array_val.len); - for (array_val) |elem_val| { + try names.ensureUnusedCapacity(sema.arena, len); + var i: usize = 0; + while (i < len) : (i += 1) { + var buf: Value.ElemValueBuffer = undefined; + const elem_val = slice_val.ptr.elemValueBuffer(mod, i, &buf); const struct_val = elem_val.castTag(.aggregate).?.data; // TODO use reflection instead of magic numbers here // error_set: type, @@ -17416,14 +17429,14 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in var buf: Value.ToTypeBuffer = undefined; const args_slice_val = args_val.castTag(.slice).?.data; - const args_decl_index = args_slice_val.ptr.pointerDecl().?; - try sema.ensureDeclAnalyzed(args_decl_index); - const args_decl = mod.declPtr(args_decl_index); - const args: []Value = if (args_decl.val.castTag(.aggregate)) |some| some.data else &.{}; - var param_types = try sema.arena.alloc(Type, args.len); - var comptime_params = try sema.arena.alloc(bool, args.len); + const args_len = try sema.usizeCast(block, src, args_slice_val.len.toUnsignedInt(mod.getTarget())); + var param_types = try sema.arena.alloc(Type, args_len); + var comptime_params = try sema.arena.alloc(bool, args_len); var noalias_bits: u32 = 0; - for (args) |arg, i| { + var i: usize = 0; + while (i < args_len) : (i += 1) { + var arg_buf: Value.ElemValueBuffer = undefined; + const arg = args_slice_val.ptr.elemValueBuffer(mod, i, &arg_buf); const arg_val = arg.castTag(.aggregate).?.data; // TODO use reflection instead of magic numbers here // is_generic: bool, @@ -20841,9 +20854,9 @@ fn validateExternType( .Opaque, .Bool, .Float, - .Pointer, .AnyFrame, => return true, + .Pointer => return !ty.isSlice(), .Int => switch (ty.intInfo(sema.mod.getTarget()).bits) { 8, 16, 32, 64, 128 => return true, else => return false, @@ -20886,7 +20899,6 @@ fn explainWhyTypeIsNotExtern( .Opaque, .Bool, .Float, - .Pointer, .AnyFrame, => return, @@ -20902,6 +20914,7 @@ fn explainWhyTypeIsNotExtern( .Frame, => return, + .Pointer => try mod.errNoteNonLazy(src_loc, msg, "slices have no guaranteed in-memory representation", .{}), .Void => try mod.errNoteNonLazy(src_loc, msg, "'void' is a zero bit type; for C 'void' use 'anyopaque'", .{}), .NoReturn => try mod.errNoteNonLazy(src_loc, msg, "'noreturn' is only allowed as a return type", .{}), .Int => if (ty.intInfo(sema.mod.getTarget()).bits > 128) { @@ -20960,11 +20973,11 @@ fn validatePackedType(ty: Type) bool { .Void, .Bool, .Float, - .Pointer, .Int, .Vector, .Enum, => return true, + .Pointer => return !ty.isSlice(), .Struct, .Union => return ty.containerLayout() == .Packed, } } @@ -20980,7 +20993,6 @@ fn explainWhyTypeIsNotPacked( .Void, .Bool, .Float, - .Pointer, .Int, .Vector, .Enum, @@ -21001,6 +21013,7 @@ fn explainWhyTypeIsNotPacked( .Optional, .Array, => try mod.errNoteNonLazy(src_loc, msg, "type has no guaranteed in-memory representation", .{}), + .Pointer => try mod.errNoteNonLazy(src_loc, msg, "slices have no guaranteed in-memory representation", .{}), .Fn => { try mod.errNoteNonLazy(src_loc, msg, "type has no guaranteed in-memory representation", .{}); try mod.errNoteNonLazy(src_loc, msg, "use '*const ' to make a function pointer type", .{}); @@ -22027,6 +22040,7 @@ fn structFieldPtrByIndex( var ptr_ty_data: Type.Payload.Pointer.Data = .{ .pointee_type = field.ty, .mutable = struct_ptr_ty_info.mutable, + .@"volatile" = struct_ptr_ty_info.@"volatile", .@"addrspace" = struct_ptr_ty_info.@"addrspace", }; @@ -22246,6 +22260,7 @@ fn unionFieldPtr( const ptr_field_ty = try Type.ptr(arena, sema.mod, .{ .pointee_type = field.ty, .mutable = union_ptr_ty.ptrIsMutable(), + .@"volatile" = union_ptr_ty.isVolatilePtr(), .@"addrspace" = union_ptr_ty.ptrAddressSpace(), }); const enum_field_index = @intCast(u32, union_obj.tag_ty.enumFieldIndex(field_name).?); @@ -22568,6 +22583,7 @@ fn tupleFieldPtr( const ptr_field_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = field_ty, .mutable = tuple_ptr_ty.ptrIsMutable(), + .@"volatile" = tuple_ptr_ty.isVolatilePtr(), .@"addrspace" = tuple_ptr_ty.ptrAddressSpace(), }); @@ -23011,9 +23027,37 @@ fn coerceExtra( const dest_is_mut = dest_info.mutable; const dst_elem_type = dest_info.pointee_type; - switch (try sema.coerceInMemoryAllowed(block, dst_elem_type, array_elem_type, dest_is_mut, target, dest_ty_src, inst_src)) { + const elem_res = try sema.coerceInMemoryAllowed(block, dst_elem_type, array_elem_type, dest_is_mut, target, dest_ty_src, inst_src); + switch (elem_res) { .ok => {}, - else => break :src_array_ptr, + else => { + in_memory_result = .{ .ptr_child = .{ + .child = try elem_res.dupe(sema.arena), + .actual = array_elem_type, + .wanted = dst_elem_type, + } }; + break :src_array_ptr; + }, + } + + if (dest_info.sentinel) |dest_sent| { + if (array_ty.sentinel()) |inst_sent| { + if (!dest_sent.eql(inst_sent, dst_elem_type, sema.mod)) { + in_memory_result = .{ .ptr_sentinel = .{ + .actual = inst_sent, + .wanted = dest_sent, + .ty = dst_elem_type, + } }; + break :src_array_ptr; + } + } else { + in_memory_result = .{ .ptr_sentinel = .{ + .actual = Value.initTag(.unreachable_value), + .wanted = dest_sent, + .ty = dst_elem_type, + } }; + break :src_array_ptr; + } } switch (dest_info.size) { @@ -23027,17 +23071,7 @@ fn coerceExtra( }, .Many => { // *[N]T to [*]T - // *[N:s]T to [*:s]T - // *[N:s]T to [*]T - if (dest_info.sentinel) |dst_sentinel| { - if (array_ty.sentinel()) |src_sentinel| { - if (src_sentinel.eql(dst_sentinel, dst_elem_type, sema.mod)) { - return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); - } - } - } else { - return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); - } + return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); }, .One => {}, } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b2b811beba..b2b9d7de2c 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -5972,7 +5972,9 @@ pub const FuncGen = struct { } if (!std.mem.eql(u8, name, "_")) { - name_map.putAssumeCapacityNoClobber(name, total_i); + const gop = name_map.getOrPutAssumeCapacity(name); + if (gop.found_existing) return self.todo("duplicate asm output name '{s}'", .{name}); + gop.value_ptr.* = total_i; } total_i += 1; } @@ -6028,7 +6030,9 @@ pub const FuncGen = struct { } if (!std.mem.eql(u8, name, "_")) { - name_map.putAssumeCapacityNoClobber(name, total_i); + const gop = name_map.getOrPutAssumeCapacity(name); + if (gop.found_existing) return self.todo("duplicate asm input name '{s}'", .{name}); + gop.value_ptr.* = total_i; } // In the case of indirect inputs, LLVM requires the callsite to have diff --git a/test/behavior.zig b/test/behavior.zig index 2f1c609a55..648757d56f 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -93,6 +93,10 @@ test { _ = @import("behavior/bugs/12794.zig"); _ = @import("behavior/bugs/12801-1.zig"); _ = @import("behavior/bugs/12801-2.zig"); + _ = @import("behavior/bugs/12885.zig"); + _ = @import("behavior/bugs/12911.zig"); + _ = @import("behavior/bugs/12928.zig"); + _ = @import("behavior/bugs/12945.zig"); _ = @import("behavior/byteswap.zig"); _ = @import("behavior/byval_arg_var.zig"); _ = @import("behavior/call.zig"); diff --git a/test/behavior/bugs/12885.zig b/test/behavior/bugs/12885.zig new file mode 100644 index 0000000000..f1cc8f94b3 --- /dev/null +++ b/test/behavior/bugs/12885.zig @@ -0,0 +1,36 @@ +const std = @import("std"); +const builtin = std.builtin; +const expect = std.testing.expect; + +const info = .{ + .args = [_]builtin.Type.Error{ + .{ .name = "bar" }, + }, +}; +const Foo = @Type(.{ + .ErrorSet = &info.args, +}); +test "ErrorSet comptime_field_ptr" { + if (@import("builtin").zig_backend == .stage1) return error.SkipZigTest; + + try expect(Foo == error{bar}); +} + +const fn_info = .{ + .args = [_]builtin.Type.Fn.Param{ + .{ .is_generic = false, .is_noalias = false, .arg_type = u8 }, + }, +}; +const Bar = @Type(.{ + .Fn = .{ + .calling_convention = .Unspecified, + .alignment = 0, + .is_generic = false, + .is_var_args = false, + .return_type = void, + .args = &fn_info.args, + }, +}); +test "fn comptime_field_ptr" { + try expect(@typeInfo(Bar) == .Fn); +} diff --git a/test/behavior/bugs/12911.zig b/test/behavior/bugs/12911.zig new file mode 100644 index 0000000000..13e2dc19e4 --- /dev/null +++ b/test/behavior/bugs/12911.zig @@ -0,0 +1,11 @@ +const builtin = @import("builtin"); + +const Item = struct { field: u8 }; +const Thing = struct { + array: [1]Item, +}; +test { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + + _ = Thing{ .array = undefined }; +} diff --git a/test/behavior/bugs/12928.zig b/test/behavior/bugs/12928.zig new file mode 100644 index 0000000000..56eca8cf60 --- /dev/null +++ b/test/behavior/bugs/12928.zig @@ -0,0 +1,26 @@ +const std = @import("std"); +const expect = std.testing.expect; +const A = extern struct { + value: *volatile B, +}; +const B = extern struct { + a: u32, + b: i32, +}; +test { + var a: *A = undefined; + try expect(@TypeOf(&a.value.a) == *volatile u32); + try expect(@TypeOf(&a.value.b) == *volatile i32); +} +const C = extern struct { + value: *volatile D, +}; +const D = extern union { + a: u32, + b: i32, +}; +test { + var c: *C = undefined; + try expect(@TypeOf(&c.value.a) == *volatile u32); + try expect(@TypeOf(&c.value.b) == *volatile i32); +} diff --git a/test/behavior/bugs/12945.zig b/test/behavior/bugs/12945.zig new file mode 100644 index 0000000000..4eb7701ba4 --- /dev/null +++ b/test/behavior/bugs/12945.zig @@ -0,0 +1,13 @@ +const std = @import("std"); +const expect = std.testing.expect; + +fn A( + comptime T: type, + comptime destroycb: ?*const fn (?*T) callconv(.C) void, +) !void { + try expect(destroycb == null); +} + +test { + try A(u32, null); +} diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index b3c399cea8..9ec949bbf5 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -369,3 +369,16 @@ test "extern function used as generic parameter" { }; try expect(S.baz(S.foo) != S.baz(S.bar)); } + +test "generic struct as parameter type" { + const S = struct { + fn doTheTest(comptime Int: type, thing: struct { int: Int }) !void { + try expect(thing.int == 123); + } + fn doTheTest2(comptime Int: type, comptime thing: struct { int: Int }) !void { + try expect(thing.int == 456); + } + }; + try S.doTheTest(u32, .{ .int = 123 }); + try S.doTheTest2(i32, .{ .int = 456 }); +} diff --git a/test/behavior/typename.zig b/test/behavior/typename.zig index 63e36488e0..9b7a899653 100644 --- a/test/behavior/typename.zig +++ b/test/behavior/typename.zig @@ -246,3 +246,28 @@ test "comptime parameters not converted to anytype in function type" { const T = fn (fn (type) void, void) void; try expectEqualStrings("fn(comptime fn(comptime type) void, void) void", @typeName(T)); } + +test "anon name strategy used in sub expression" { + if (builtin.zig_backend == .stage1) { + // stage1 uses line/column for the names but we're moving away from that for + // incremental compilation purposes. + return error.SkipZigTest; + } + + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + + const S = struct { + fn getTheName() []const u8 { + return struct { + const name = @typeName(@This()); + }.name; + } + }; + try expectEqualStringsIgnoreDigits( + "behavior.typename.test.anon name strategy used in sub expression.S.getTheName__struct_0", + S.getTheName(), + ); +} diff --git a/test/cases/compile_errors/implicit_array_ptr_cast_sentinel_mismatch.zig b/test/cases/compile_errors/implicit_array_ptr_cast_sentinel_mismatch.zig new file mode 100644 index 0000000000..ff7a2737dc --- /dev/null +++ b/test/cases/compile_errors/implicit_array_ptr_cast_sentinel_mismatch.zig @@ -0,0 +1,23 @@ +fn foo() [:0xff]const u8 { + return "bark"; +} +fn bar() [:0]const u16 { + return "bark"; +} +pub export fn entry() void { + _ = foo(); +} +pub export fn entry1() void { + _ = bar(); +} + +// error +// backend=stage2 +// target=native +// +// :2:12: error: expected type '[:255]const u8', found '*const [4:0]u8' +// :2:12: note: pointer sentinel '0' cannot cast into pointer sentinel '255' +// :1:10: note: function return type declared here +// :5:12: error: expected type '[:0]const u16', found '*const [4:0]u8' +// :5:12: note: pointer type child 'u8' cannot cast into pointer type child 'u16' +// :4:10: note: function return type declared here diff --git a/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig b/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig index 8fcd300629..fd98db04c8 100644 --- a/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig +++ b/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig @@ -60,6 +60,11 @@ const U = extern union { A: i32, B: u32, }; +export fn entry12() void { + _ = @sizeOf(packed struct { + x: packed struct { a: []u8 }, + }); +} // error // backend=llvm @@ -82,3 +87,5 @@ const U = extern union { // :38:9: error: packed structs cannot contain fields of type 'fn() void' // :38:9: note: type has no guaranteed in-memory representation // :38:9: note: use '*const ' to make a function pointer type +// :65:28: error: packed structs cannot contain fields of type '[]u8' +// :65:28: note: slices have no guaranteed in-memory representation diff --git a/test/cases/compile_errors/slice_used_as_extern_fn_param.zig b/test/cases/compile_errors/slice_used_as_extern_fn_param.zig new file mode 100644 index 0000000000..8391c3e21b --- /dev/null +++ b/test/cases/compile_errors/slice_used_as_extern_fn_param.zig @@ -0,0 +1,11 @@ +extern fn Text(str: []const u8, num: i32) callconv(.C) void; +export fn entry() void { + _ = Text; +} + +// error +// backend=stage2 +// target=native +// +// :1:16: error: parameter of type '[]const u8' not allowed in function with calling convention 'C' +// :1:16: note: slices have no guaranteed in-memory representation