From 2f07d76eee37442f53c294f53b38b11dfb1cd4da Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Apr 2021 21:17:23 -0700 Subject: stage2: implement Type.onePossibleValue for structs --- src/type.zig | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src/type.zig') diff --git a/src/type.zig b/src/type.zig index 2086567b8a..f5ce296e4d 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3118,8 +3118,14 @@ pub const Type = extern union { => return null, .@"struct" => { - log.warn("TODO implement Type.onePossibleValue for structs", .{}); - return null; + const s = ty.castTag(.@"struct").?.data; + for (s.fields.entries.items) |entry| { + const field_ty = entry.value.ty; + if (field_ty.onePossibleValue() == null) { + return null; + } + } + return Value.initTag(.empty_struct_value); }, .empty_struct, .empty_struct_literal => return Value.initTag(.empty_struct_value), -- cgit v1.2.3 From b40d36c90ba894a12f2de4e6c881642edffad3ed Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 6 Apr 2021 17:43:56 -0700 Subject: stage2: implement simple enums A simple enum is an enum which has an automatic integer tag type, all tag values automatically assigned, and no top level declarations. Such enums are created directly in AstGen and shared by all the generic/comptime instantiations of the surrounding ZIR code. This commit implements, but does not yet add any test cases for, simple enums. A full enum is an enum for which any of the above conditions are not true. Full enums are created in Sema, and therefore will create a unique type per generic/comptime instantiation. This commit does not implement full enums. However the `enum_decl_nonexhaustive` ZIR instruction is added and the respective Type functions are filled out. This commit makes an improvement to ZIR code, removing the decls array and removing the decl_map from AstGen. Instead, decl_ref and decl_val ZIR instructions index into the `owner_decl.dependencies` ArrayHashMap. We already need this dependencies array for incremental compilation purposes, and so repurposing it to also use it for ZIR decl indexes makes for efficient memory usage. Similarly, this commit fixes up incorrect memory management by removing the `const` ZIR instruction. The two places it was used stored memory in the AstGen arena, which may get freed after Sema. Now it properly sets up a new anonymous Decl for error sets and uses a normal decl_val instruction. The other usage of `const` ZIR instruction was float literals. These are now changed to use `float` ZIR instruction when the value fits inside `zir.Inst.Data` and `float128` otherwise. AstGen + Sema: implement int_to_enum and enum_to_int. No tests yet; I expect to have to make some fixes before they will pass tests. Will do that in the branch before merging. AstGen: fix struct astgen incorrectly counting decls as fields. Type/Value: give up on trying to exhaustively list every tag all the time. This makes the file more manageable. Also found a bug with i128/u128 this way, since the name of the function was more obvious when looking at the tag values. Type: implement abiAlignment and abiSize for structs. This will need to get more sophisticated at some point, but for now it is progress. Value: add new `enum_field_index` tag. Value: add hash_u32, needed when using ArrayHashMap. --- src/AstGen.zig | 290 +++++++-- src/BuiltinFn.zig | 2 +- src/Module.zig | 96 ++- src/Sema.zig | 172 ++++- src/type.zig | 1857 +++++++++-------------------------------------------- src/value.zig | 837 ++---------------------- src/zir.zig | 87 ++- 7 files changed, 898 insertions(+), 2443 deletions(-) (limited to 'src/type.zig') diff --git a/src/AstGen.zig b/src/AstGen.zig index 6c3ce1251c..af9938d425 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -28,8 +28,6 @@ const BuiltinFn = @import("BuiltinFn.zig"); instructions: std.MultiArrayList(zir.Inst) = .{}, string_bytes: ArrayListUnmanaged(u8) = .{}, extra: ArrayListUnmanaged(u32) = .{}, -decl_map: std.StringArrayHashMapUnmanaged(void) = .{}, -decls: ArrayListUnmanaged(*Decl) = .{}, /// The end of special indexes. `zir.Inst.Ref` subtracts against this number to convert /// to `zir.Inst.Index`. The default here is correct if there are 0 parameters. ref_start_index: u32 = zir.Inst.Ref.typed_value_map.len, @@ -110,8 +108,6 @@ pub fn deinit(astgen: *AstGen) void { astgen.instructions.deinit(gpa); astgen.extra.deinit(gpa); astgen.string_bytes.deinit(gpa); - astgen.decl_map.deinit(gpa); - astgen.decls.deinit(gpa); } pub const ResultLoc = union(enum) { @@ -1183,13 +1179,6 @@ fn blockExprStmts( // in the above while loop. const zir_tags = gz.astgen.instructions.items(.tag); switch (zir_tags[inst]) { - .@"const" => { - const tv = gz.astgen.instructions.items(.data)[inst].@"const"; - break :b switch (tv.ty.zigTypeTag()) { - .NoReturn, .Void => true, - else => false, - }; - }, // For some instructions, swap in a slightly different ZIR tag // so we can avoid a separate ensure_result_used instruction. .call_none_chkused => unreachable, @@ -1257,6 +1246,8 @@ fn blockExprStmts( .fn_type_cc, .fn_type_cc_var_args, .int, + .float, + .float128, .intcast, .int_type, .is_non_null, @@ -1334,7 +1325,10 @@ fn blockExprStmts( .struct_decl_extern, .union_decl, .enum_decl, + .enum_decl_nonexhaustive, .opaque_decl, + .int_to_enum, + .enum_to_int, => break :b false, // ZIR instructions that are always either `noreturn` or `void`. @@ -1823,15 +1817,18 @@ fn containerDecl( defer bit_bag.deinit(gpa); var cur_bit_bag: u32 = 0; - var member_index: usize = 0; - while (true) { - const member_node = container_decl.ast.members[member_index]; + var field_index: usize = 0; + for (container_decl.ast.members) |member_node| { const member = switch (node_tags[member_node]) { .container_field_init => tree.containerFieldInit(member_node), .container_field_align => tree.containerFieldAlign(member_node), .container_field => tree.containerField(member_node), - else => unreachable, + else => continue, }; + if (field_index % 16 == 0 and field_index != 0) { + try bit_bag.append(gpa, cur_bit_bag); + cur_bit_bag = 0; + } if (member.comptime_token) |comptime_token| { return mod.failTok(scope, comptime_token, "TODO implement comptime struct fields", .{}); } @@ -1858,17 +1855,9 @@ fn containerDecl( fields_data.appendAssumeCapacity(@enumToInt(default_inst)); } - member_index += 1; - if (member_index < container_decl.ast.members.len) { - if (member_index % 16 == 0) { - try bit_bag.append(gpa, cur_bit_bag); - cur_bit_bag = 0; - } - } else { - break; - } + field_index += 1; } - const empty_slot_count = 16 - ((member_index - 1) % 16); + const empty_slot_count = 16 - ((field_index - 1) % 16); cur_bit_bag >>= @intCast(u5, empty_slot_count * 2); const result = try gz.addPlNode(tag, node, zir.Inst.StructDecl{ @@ -1885,7 +1874,172 @@ fn containerDecl( return mod.failTok(scope, container_decl.ast.main_token, "TODO AstGen for union decl", .{}); }, .keyword_enum => { - return mod.failTok(scope, container_decl.ast.main_token, "TODO AstGen for enum decl", .{}); + if (container_decl.layout_token) |t| { + return mod.failTok(scope, t, "enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type", .{}); + } + // Count total fields as well as how many have explicitly provided tag values. + const counts = blk: { + var values: usize = 0; + var total_fields: usize = 0; + var decls: usize = 0; + var nonexhaustive_node: ast.Node.Index = 0; + for (container_decl.ast.members) |member_node| { + const member = switch (node_tags[member_node]) { + .container_field_init => tree.containerFieldInit(member_node), + .container_field_align => tree.containerFieldAlign(member_node), + .container_field => tree.containerField(member_node), + else => { + decls += 1; + continue; + }, + }; + if (member.comptime_token) |comptime_token| { + return mod.failTok(scope, comptime_token, "enum fields cannot be marked comptime", .{}); + } + if (member.ast.type_expr != 0) { + return mod.failNode(scope, member.ast.type_expr, "enum fields do not have types", .{}); + } + if (member.ast.align_expr != 0) { + return mod.failNode(scope, member.ast.align_expr, "enum fields do not have alignments", .{}); + } + const name_token = member.ast.name_token; + if (mem.eql(u8, tree.tokenSlice(name_token), "_")) { + if (nonexhaustive_node != 0) { + const msg = msg: { + const msg = try mod.errMsg( + scope, + gz.nodeSrcLoc(member_node), + "redundant non-exhaustive enum mark", + .{}, + ); + errdefer msg.destroy(gpa); + const other_src = gz.nodeSrcLoc(nonexhaustive_node); + try mod.errNote(scope, other_src, msg, "other mark here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); + } + nonexhaustive_node = member_node; + if (member.ast.value_expr != 0) { + return mod.failNode(scope, member.ast.value_expr, "'_' is used to mark an enum as non-exhaustive and cannot be assigned a value", .{}); + } + continue; + } + total_fields += 1; + if (member.ast.value_expr != 0) { + values += 1; + } + } + break :blk .{ + .total_fields = total_fields, + .values = values, + .decls = decls, + .nonexhaustive_node = nonexhaustive_node, + }; + }; + if (counts.total_fields == 0) { + // One can construct an enum with no tags, and it functions the same as `noreturn`. But + // this is only useful for generic code; when explicitly using `enum {}` syntax, there + // must be at least one tag. + return mod.failNode(scope, node, "enum declarations must have at least one tag", .{}); + } + if (counts.nonexhaustive_node != 0 and arg_inst == .none) { + const msg = msg: { + const msg = try mod.errMsg( + scope, + gz.nodeSrcLoc(node), + "non-exhaustive enum missing integer tag type", + .{}, + ); + errdefer msg.destroy(gpa); + const other_src = gz.nodeSrcLoc(counts.nonexhaustive_node); + try mod.errNote(scope, other_src, msg, "marked non-exhaustive here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); + } + if (counts.values == 0 and counts.decls == 0 and arg_inst == .none) { + // No explicitly provided tag values and no top level declarations! In this case, + // we can construct the enum type in AstGen and it will be correctly shared by all + // generic function instantiations and comptime function calls. + var new_decl_arena = std.heap.ArenaAllocator.init(gpa); + errdefer new_decl_arena.deinit(); + const arena = &new_decl_arena.allocator; + + var fields_map: std.StringArrayHashMapUnmanaged(void) = .{}; + try fields_map.ensureCapacity(arena, counts.total_fields); + for (container_decl.ast.members) |member_node| { + if (member_node == counts.nonexhaustive_node) + continue; + const member = switch (node_tags[member_node]) { + .container_field_init => tree.containerFieldInit(member_node), + .container_field_align => tree.containerFieldAlign(member_node), + .container_field => tree.containerField(member_node), + else => unreachable, // We checked earlier. + }; + const name_token = member.ast.name_token; + const tag_name = try mod.identifierTokenStringTreeArena( + scope, + name_token, + tree, + arena, + ); + const gop = fields_map.getOrPutAssumeCapacity(tag_name); + if (gop.found_existing) { + const msg = msg: { + const msg = try mod.errMsg( + scope, + gz.tokSrcLoc(name_token), + "duplicate enum tag", + .{}, + ); + errdefer msg.destroy(gpa); + // Iterate to find the other tag. We don't eagerly store it in a hash + // map because in the hot path there will be no compile error and we + // don't need to waste time with a hash map. + const bad_node = for (container_decl.ast.members) |other_member_node| { + const other_member = switch (node_tags[other_member_node]) { + .container_field_init => tree.containerFieldInit(member_node), + .container_field_align => tree.containerFieldAlign(member_node), + .container_field => tree.containerField(member_node), + else => unreachable, // We checked earlier. + }; + const other_tag_name = try mod.identifierTokenStringTreeArena( + scope, + name_token, + tree, + arena, + ); + if (mem.eql(u8, tag_name, other_tag_name)) + break other_member_node; + } else unreachable; + const other_src = gz.nodeSrcLoc(bad_node); + try mod.errNote(scope, other_src, msg, "other tag here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); + } + } + const enum_simple = try arena.create(Module.EnumSimple); + enum_simple.* = .{ + .owner_decl = astgen.decl, + .node_offset = astgen.decl.nodeIndexToRelative(node), + .fields = fields_map, + }; + const enum_ty = try Type.Tag.enum_simple.create(arena, enum_simple); + const enum_val = try Value.Tag.ty.create(arena, enum_ty); + const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{ + .ty = Type.initTag(.type), + .val = enum_val, + }); + const decl_index = try mod.declareDeclDependency(astgen.decl, new_decl); + const result = try gz.addDecl(.decl_val, decl_index, node); + return rvalue(gz, scope, rl, result, node); + } + // In this case we must generate ZIR code for the tag values, similar to + // how structs are handled above. The new anonymous Decl will be created in + // Sema, not AstGen. + return mod.failNode(scope, node, "TODO AstGen for enum decl with decls or explicitly provided field values", .{}); }, .keyword_opaque => { const result = try gz.addNode(.opaque_decl, node); @@ -1901,11 +2055,11 @@ fn errorSetDecl( rl: ResultLoc, node: ast.Node.Index, ) InnerError!zir.Inst.Ref { - const mod = gz.astgen.mod; + const astgen = gz.astgen; + const mod = astgen.mod; const tree = gz.tree(); const main_tokens = tree.nodes.items(.main_token); const token_tags = tree.tokens.items(.tag); - const arena = gz.astgen.arena; // Count how many fields there are. const error_token = main_tokens[node]; @@ -1922,6 +2076,11 @@ fn errorSetDecl( } else unreachable; // TODO should not need else unreachable here }; + const gpa = mod.gpa; + var new_decl_arena = std.heap.ArenaAllocator.init(gpa); + errdefer new_decl_arena.deinit(); + const arena = &new_decl_arena.allocator; + const fields = try arena.alloc([]const u8, count); { var tok_i = error_token + 2; @@ -1930,7 +2089,7 @@ fn errorSetDecl( switch (token_tags[tok_i]) { .doc_comment, .comma => {}, .identifier => { - fields[field_i] = try mod.identifierTokenString(scope, tok_i); + fields[field_i] = try mod.identifierTokenStringTreeArena(scope, tok_i, tree, arena); field_i += 1; }, .r_brace => break, @@ -1940,18 +2099,19 @@ fn errorSetDecl( } const error_set = try arena.create(Module.ErrorSet); error_set.* = .{ - .owner_decl = gz.astgen.decl, - .node_offset = gz.astgen.decl.nodeIndexToRelative(node), + .owner_decl = astgen.decl, + .node_offset = astgen.decl.nodeIndexToRelative(node), .names_ptr = fields.ptr, .names_len = @intCast(u32, fields.len), }; const error_set_ty = try Type.Tag.error_set.create(arena, error_set); - const typed_value = try arena.create(TypedValue); - typed_value.* = .{ + const error_set_val = try Value.Tag.ty.create(arena, error_set_ty); + const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{ .ty = Type.initTag(.type), - .val = try Value.Tag.ty.create(arena, error_set_ty), - }; - const result = try gz.addConst(typed_value); + .val = error_set_val, + }); + const decl_index = try mod.declareDeclDependency(astgen.decl, new_decl); + const result = try gz.addDecl(.decl_val, decl_index, node); return rvalue(gz, scope, rl, result, node); } @@ -3426,7 +3586,8 @@ fn identifier( const tracy = trace(@src()); defer tracy.end(); - const mod = gz.astgen.mod; + const astgen = gz.astgen; + const mod = astgen.mod; const tree = gz.tree(); const main_tokens = tree.nodes.items(.main_token); @@ -3459,7 +3620,7 @@ fn identifier( const result = try gz.add(.{ .tag = .int_type, .data = .{ .int_type = .{ - .src_node = gz.astgen.decl.nodeIndexToRelative(ident), + .src_node = astgen.decl.nodeIndexToRelative(ident), .signedness = signedness, .bit_count = bit_count, } }, @@ -3497,13 +3658,13 @@ fn identifier( }; } - const gop = try gz.astgen.decl_map.getOrPut(mod.gpa, ident_name); - if (!gop.found_existing) { - const decl = mod.lookupDeclName(scope, ident_name) orelse - return mod.failNode(scope, ident, "use of undeclared identifier '{s}'", .{ident_name}); - try gz.astgen.decls.append(mod.gpa, decl); - } - const decl_index = @intCast(u32, gop.index); + const decl = mod.lookupDeclName(scope, ident_name) orelse { + // TODO insert a "dependency on the non-existence of a decl" here to make this + // compile error go away when the decl is introduced. This data should be in a global + // sparse map since it is only relevant when a compile error occurs. + return mod.failNode(scope, ident, "use of undeclared identifier '{s}'", .{ident_name}); + }; + const decl_index = try mod.declareDeclDependency(astgen.decl, decl); switch (rl) { .ref, .none_or_ref => return gz.addDecl(.decl_ref, decl_index, ident), else => return rvalue(gz, scope, rl, try gz.addDecl(.decl_val, decl_index, ident), ident), @@ -3638,12 +3799,23 @@ fn floatLiteral( const float_number = std.fmt.parseFloat(f128, bytes) catch |e| switch (e) { error.InvalidCharacter => unreachable, // validated by tokenizer }; - const typed_value = try arena.create(TypedValue); - typed_value.* = .{ - .ty = Type.initTag(.comptime_float), - .val = try Value.Tag.float_128.create(arena, float_number), - }; - const result = try gz.addConst(typed_value); + // If the value fits into a f32 without losing any precision, store it that way. + @setFloatMode(.Strict); + const smaller_float = @floatCast(f32, float_number); + const bigger_again: f128 = smaller_float; + if (bigger_again == float_number) { + const result = try gz.addFloat(smaller_float, node); + return rvalue(gz, scope, rl, result, node); + } + // We need to use 128 bits. Break the float into 4 u32 values so we can + // put it into the `extra` array. + const int_bits = @bitCast(u128, float_number); + const result = try gz.addPlNode(.float128, node, zir.Inst.Float128{ + .piece0 = @truncate(u32, int_bits), + .piece1 = @truncate(u32, int_bits >> 32), + .piece2 = @truncate(u32, int_bits >> 64), + .piece3 = @truncate(u32, int_bits >> 96), + }); return rvalue(gz, scope, rl, result, node); } @@ -3955,6 +4127,20 @@ fn builtinCall( .bit_cast => return bitCast(gz, scope, rl, node, params[0], params[1]), .TypeOf => return typeOf(gz, scope, rl, node, params), + .int_to_enum => { + const result = try gz.addPlNode(.int_to_enum, node, zir.Inst.Bin{ + .lhs = try typeExpr(gz, scope, params[0]), + .rhs = try expr(gz, scope, .none, params[1]), + }); + return rvalue(gz, scope, rl, result, node); + }, + + .enum_to_int => { + const operand = try expr(gz, scope, .none, params[0]); + const result = try gz.addUnNode(.enum_to_int, operand, node); + return rvalue(gz, scope, rl, result, node); + }, + .add_with_overflow, .align_cast, .align_of, @@ -3981,7 +4167,6 @@ fn builtinCall( .div_floor, .div_trunc, .embed_file, - .enum_to_int, .error_name, .error_return_trace, .err_set_cast, @@ -3991,7 +4176,6 @@ fn builtinCall( .float_to_int, .has_decl, .has_field, - .int_to_enum, .int_to_float, .int_to_ptr, .memcpy, diff --git a/src/BuiltinFn.zig b/src/BuiltinFn.zig index 1710169dc7..a5d15554af 100644 --- a/src/BuiltinFn.zig +++ b/src/BuiltinFn.zig @@ -484,7 +484,7 @@ pub const list = list: { "@intToEnum", .{ .tag = .int_to_enum, - .param_count = 1, + .param_count = 2, }, }, .{ diff --git a/src/Module.zig b/src/Module.zig index bab61730e5..8c58b63995 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -290,6 +290,18 @@ pub const Decl = struct { return decl.container.fullyQualifiedNameHash(mem.spanZ(decl.name)); } + pub fn renderFullyQualifiedName(decl: Decl, writer: anytype) !void { + const unqualified_name = mem.spanZ(decl.name); + return decl.container.renderFullyQualifiedName(unqualified_name, writer); + } + + pub fn getFullyQualifiedName(decl: Decl, gpa: *Allocator) ![]u8 { + var buffer = std.ArrayList(u8).init(gpa); + defer buffer.deinit(); + try decl.renderFullyQualifiedName(buffer.writer()); + return buffer.toOwnedSlice(); + } + pub fn typedValue(decl: *Decl) error{AnalysisFail}!TypedValue { const tvm = decl.typedValueManaged() orelse return error.AnalysisFail; return tvm.typed_value; @@ -375,8 +387,7 @@ pub const Struct = struct { }; pub fn getFullyQualifiedName(s: *Struct, gpa: *Allocator) ![]u8 { - // TODO this should return e.g. "std.fs.Dir.OpenOptions" - return gpa.dupe(u8, mem.spanZ(s.owner_decl.name)); + return s.owner_decl.getFullyQualifiedName(gpa); } pub fn srcLoc(s: Struct) SrcLoc { @@ -387,6 +398,39 @@ pub const Struct = struct { } }; +/// Represents the data that an enum declaration provides, when the fields +/// are auto-numbered, and there are no declarations. The integer tag type +/// is inferred to be the smallest power of two unsigned int that fits +/// the number of fields. +pub const EnumSimple = struct { + owner_decl: *Decl, + /// Set of field names in declaration order. + fields: std.StringArrayHashMapUnmanaged(void), + /// Offset from `owner_decl`, points to the enum decl AST node. + node_offset: i32, +}; + +/// Represents the data that an enum declaration provides, when there is +/// at least one tag value explicitly specified, or at least one declaration. +pub const EnumFull = struct { + owner_decl: *Decl, + /// An integer type which is used for the numerical value of the enum. + /// Whether zig chooses this type or the user specifies it, it is stored here. + tag_ty: Type, + /// Set of field names in declaration order. + fields: std.StringArrayHashMapUnmanaged(void), + /// Maps integer tag value to field index. + /// Entries are in declaration order, same as `fields`. + /// If this hash map is empty, it means the enum tags are auto-numbered. + values: ValueMap, + /// Represents the declarations inside this struct. + container: Scope.Container, + /// Offset from `owner_decl`, points to the enum decl AST node. + node_offset: i32, + + pub const ValueMap = std.ArrayHashMapUnmanaged(Value, void, Value.hash_u32, Value.eql, false); +}; + /// Some Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. /// Extern functions do not have this data structure; they are represented by /// the `Decl` only, with a `Value` tag of `extern_fn`. @@ -634,6 +678,11 @@ pub const Scope = struct { // TODO container scope qualified names. return std.zig.hashSrc(name); } + + pub fn renderFullyQualifiedName(cont: Container, name: []const u8, writer: anytype) !void { + // TODO this should render e.g. "std.fs.Dir.OpenOptions" + return writer.writeAll(name); + } }; pub const File = struct { @@ -1030,7 +1079,6 @@ pub const Scope = struct { .instructions = gz.astgen.instructions.toOwnedSlice(), .string_bytes = gz.astgen.string_bytes.toOwnedSlice(gpa), .extra = gz.astgen.extra.toOwnedSlice(gpa), - .decls = gz.astgen.decls.toOwnedSlice(gpa), }; } @@ -1242,6 +1290,16 @@ pub const Scope = struct { }); } + pub fn addFloat(gz: *GenZir, number: f32, src_node: ast.Node.Index) !zir.Inst.Ref { + return gz.add(.{ + .tag = .float, + .data = .{ .float = .{ + .src_node = gz.astgen.decl.nodeIndexToRelative(src_node), + .number = number, + } }, + }); + } + pub fn addUnNode( gz: *GenZir, tag: zir.Inst.Tag, @@ -1450,13 +1508,6 @@ pub const Scope = struct { return new_index; } - pub fn addConst(gz: *GenZir, typed_value: *TypedValue) !zir.Inst.Ref { - return gz.add(.{ - .tag = .@"const", - .data = .{ .@"const" = typed_value }, - }); - } - pub fn add(gz: *GenZir, inst: zir.Inst) !zir.Inst.Ref { return gz.astgen.indexToRef(try gz.addAsIndex(inst)); } @@ -3120,12 +3171,14 @@ fn astgenAndSemaVarDecl( return type_changed; } -pub fn declareDeclDependency(mod: *Module, depender: *Decl, dependee: *Decl) !void { - try depender.dependencies.ensureCapacity(mod.gpa, depender.dependencies.items().len + 1); - try dependee.dependants.ensureCapacity(mod.gpa, dependee.dependants.items().len + 1); +/// Returns the depender's index of the dependee. +pub fn declareDeclDependency(mod: *Module, depender: *Decl, dependee: *Decl) !u32 { + try depender.dependencies.ensureCapacity(mod.gpa, depender.dependencies.count() + 1); + try dependee.dependants.ensureCapacity(mod.gpa, dependee.dependants.count() + 1); - depender.dependencies.putAssumeCapacity(dependee, {}); dependee.dependants.putAssumeCapacity(depender, {}); + const gop = depender.dependencies.getOrPutAssumeCapacity(dependee); + return @intCast(u32, gop.index); } pub fn getAstTree(mod: *Module, root_scope: *Scope.File) !*const ast.Tree { @@ -4445,7 +4498,17 @@ pub fn optimizeMode(mod: Module) std.builtin.Mode { /// Otherwise, returns a reference to the source code bytes directly. /// See also `appendIdentStr` and `parseStrLit`. pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 { - const tree = scope.tree(); + return mod.identifierTokenStringTreeArena(scope, token, scope.tree(), scope.arena()); +} + +/// `scope` is only used for error reporting. +pub fn identifierTokenStringTreeArena( + mod: *Module, + scope: *Scope, + token: ast.TokenIndex, + tree: *const ast.Tree, + arena: *Allocator, +) InnerError![]const u8 { const token_tags = tree.tokens.items(.tag); assert(token_tags[token] == .identifier); const ident_name = tree.tokenSlice(token); @@ -4455,7 +4518,8 @@ pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) var buf: ArrayListUnmanaged(u8) = .{}; defer buf.deinit(mod.gpa); try parseStrLit(mod, scope, token, &buf, ident_name, 1); - return buf.toOwnedSlice(mod.gpa); + const duped = try arena.dupe(u8, buf.items); + return duped; } /// Given an identifier token, obtain the string for it (possibly parsing as a string diff --git a/src/Sema.zig b/src/Sema.zig index 0b5a21ce42..771da877b4 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -168,7 +168,6 @@ pub fn analyzeBody( .cmp_lte => try sema.zirCmp(block, inst, .lte), .cmp_neq => try sema.zirCmp(block, inst, .neq), .coerce_result_ptr => try sema.zirCoerceResultPtr(block, inst), - .@"const" => try sema.zirConst(block, inst), .decl_ref => try sema.zirDeclRef(block, inst), .decl_val => try sema.zirDeclVal(block, inst), .load => try sema.zirLoad(block, inst), @@ -179,6 +178,8 @@ pub fn analyzeBody( .elem_val_node => try sema.zirElemValNode(block, inst), .enum_literal => try sema.zirEnumLiteral(block, inst), .enum_literal_small => try sema.zirEnumLiteralSmall(block, inst), + .enum_to_int => try sema.zirEnumToInt(block, inst), + .int_to_enum => try sema.zirIntToEnum(block, inst), .err_union_code => try sema.zirErrUnionCode(block, inst), .err_union_code_ptr => try sema.zirErrUnionCodePtr(block, inst), .err_union_payload_safe => try sema.zirErrUnionPayload(block, inst, true), @@ -201,6 +202,8 @@ pub fn analyzeBody( .import => try sema.zirImport(block, inst), .indexable_ptr_len => try sema.zirIndexablePtrLen(block, inst), .int => try sema.zirInt(block, inst), + .float => try sema.zirFloat(block, inst), + .float128 => try sema.zirFloat128(block, inst), .int_type => try sema.zirIntType(block, inst), .intcast => try sema.zirIntcast(block, inst), .is_err => try sema.zirIsErr(block, inst), @@ -264,7 +267,8 @@ pub fn analyzeBody( .struct_decl => try sema.zirStructDecl(block, inst, .Auto), .struct_decl_packed => try sema.zirStructDecl(block, inst, .Packed), .struct_decl_extern => try sema.zirStructDecl(block, inst, .Extern), - .enum_decl => try sema.zirEnumDecl(block, inst), + .enum_decl => try sema.zirEnumDecl(block, inst, false), + .enum_decl_nonexhaustive => try sema.zirEnumDecl(block, inst, true), .union_decl => try sema.zirUnionDecl(block, inst), .opaque_decl => try sema.zirOpaqueDecl(block, inst), @@ -498,18 +502,6 @@ fn resolveInstConst( }; } -fn zirConst(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { - const tracy = trace(@src()); - defer tracy.end(); - - const tv_ptr = sema.code.instructions.items(.data)[inst].@"const"; - // Move the TypedValue from old memory to new memory. This allows freeing the ZIR instructions - // after analysis. This happens, for example, with variable declaration initialization - // expressions. - const typed_value_copy = try tv_ptr.copy(sema.arena); - return sema.mod.constInst(sema.arena, .unneeded, typed_value_copy); -} - fn zirBitcastResultPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); @@ -617,7 +609,12 @@ fn zirStructDecl( return sema.analyzeDeclVal(block, src, new_decl); } -fn zirEnumDecl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { +fn zirEnumDecl( + sema: *Sema, + block: *Scope.Block, + inst: zir.Inst.Index, + nonexhaustive: bool, +) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); @@ -1070,6 +1067,31 @@ fn zirInt(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*In return sema.mod.constIntUnsigned(sema.arena, .unneeded, Type.initTag(.comptime_int), int); } +fn zirFloat(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { + const arena = sema.arena; + const inst_data = sema.code.instructions.items(.data)[inst].float; + const src = inst_data.src(); + const number = inst_data.number; + + return sema.mod.constInst(arena, src, .{ + .ty = Type.initTag(.comptime_float), + .val = try Value.Tag.float_32.create(arena, number), + }); +} + +fn zirFloat128(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { + const arena = sema.arena; + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(zir.Inst.Float128, inst_data.payload_index).data; + const src = inst_data.src(); + const number = extra.get(); + + return sema.mod.constInst(arena, src, .{ + .ty = Type.initTag(.comptime_float), + .val = try Value.Tag.float_128.create(arena, number), + }); +} + fn zirCompileError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index { const tracy = trace(@src()); defer tracy.end(); @@ -1385,7 +1407,7 @@ fn zirDeclRef(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); - const decl = sema.code.decls[inst_data.payload_index]; + const decl = sema.owner_decl.dependencies.entries.items[inst_data.payload_index].key; return sema.analyzeDeclRef(block, src, decl); } @@ -1395,7 +1417,7 @@ fn zirDeclVal(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); - const decl = sema.code.decls[inst_data.payload_index]; + const decl = sema.owner_decl.dependencies.entries.items[inst_data.payload_index].key; return sema.analyzeDeclVal(block, src, decl); } @@ -1852,6 +1874,120 @@ fn zirEnumLiteralSmall(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) I }); } +fn zirEnumToInt(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { + const mod = sema.mod; + const arena = sema.arena; + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const operand = try sema.resolveInst(inst_data.operand); + + const enum_tag: *Inst = switch (operand.ty.zigTypeTag()) { + .Enum => operand, + .Union => { + //if (!operand.ty.unionHasTag()) { + // return mod.fail( + // &block.base, + // operand_src, + // "untagged union '{}' cannot be converted to integer", + // .{dest_ty_src}, + // ); + //} + return mod.fail(&block.base, operand_src, "TODO zirEnumToInt for tagged unions", .{}); + }, + else => { + return mod.fail(&block.base, operand_src, "expected enum or tagged union, found {}", .{ + operand.ty, + }); + }, + }; + + var int_tag_type_buffer: Type.Payload.Bits = undefined; + const int_tag_ty = try enum_tag.ty.intTagType(&int_tag_type_buffer).copy(arena); + + if (enum_tag.ty.onePossibleValue()) |opv| { + return mod.constInst(arena, src, .{ + .ty = int_tag_ty, + .val = opv, + }); + } + + if (enum_tag.value()) |enum_tag_val| { + if (enum_tag_val.castTag(.enum_field_index)) |enum_field_payload| { + const field_index = enum_field_payload.data; + switch (enum_tag.ty.tag()) { + .enum_full => { + const enum_full = enum_tag.ty.castTag(.enum_full).?.data; + const val = enum_full.values.entries.items[field_index].key; + return mod.constInst(arena, src, .{ + .ty = int_tag_ty, + .val = val, + }); + }, + .enum_simple => { + // Field index and integer values are the same. + const val = try Value.Tag.int_u64.create(arena, field_index); + return mod.constInst(arena, src, .{ + .ty = int_tag_ty, + .val = val, + }); + }, + else => unreachable, + } + } else { + // Assume it is already an integer and return it directly. + return mod.constInst(arena, src, .{ + .ty = int_tag_ty, + .val = enum_tag_val, + }); + } + } + + try sema.requireRuntimeBlock(block, src); + return block.addUnOp(src, int_tag_ty, .bitcast, enum_tag); +} + +fn zirIntToEnum(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { + const mod = sema.mod; + const target = mod.getTarget(); + const arena = sema.arena; + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data; + const src = inst_data.src(); + const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); + const operand = try sema.resolveInst(extra.rhs); + + if (dest_ty.zigTypeTag() != .Enum) { + return mod.fail(&block.base, dest_ty_src, "expected enum, found {}", .{dest_ty}); + } + + if (!dest_ty.isExhaustiveEnum()) { + if (operand.value()) |int_val| { + return mod.constInst(arena, src, .{ + .ty = dest_ty, + .val = int_val, + }); + } + } + + if (try sema.resolveDefinedValue(block, operand_src, operand)) |int_val| { + if (!dest_ty.enumHasInt(int_val, target)) { + return mod.fail(&block.base, src, "enum '{}' has no tag with value {}", .{ + dest_ty, int_val, + }); + } + return mod.constInst(arena, src, .{ + .ty = dest_ty, + .val = int_val, + }); + } + + try sema.requireRuntimeBlock(block, src); + return block.addUnOp(src, dest_ty, .bitcast, operand); +} + /// Pointer in, pointer out. fn zirOptionalPayloadPtr( sema: *Sema, @@ -4630,7 +4766,7 @@ fn analyzeDeclVal(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl } fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl) InnerError!*Inst { - try sema.mod.declareDeclDependency(sema.owner_decl, decl); + _ = try sema.mod.declareDeclDependency(sema.owner_decl, decl); sema.mod.ensureDeclAnalyzed(decl) catch |err| { if (sema.func) |func| { func.state = .dependency_failure; diff --git a/src/type.zig b/src/type.zig index f5ce296e4d..c338072c15 100644 --- a/src/type.zig +++ b/src/type.zig @@ -93,9 +93,15 @@ pub const Type = extern union { .anyerror_void_error_union, .error_union => return .ErrorUnion, - .empty_struct => return .Struct, - .empty_struct_literal => return .Struct, - .@"struct" => return .Struct, + .empty_struct, + .empty_struct_literal, + .@"struct", + => return .Struct, + + .enum_full, + .enum_nonexhaustive, + .enum_simple, + => return .Enum, .var_args_param => unreachable, // can be any type } @@ -614,6 +620,8 @@ pub const Type = extern union { .error_set_single => return self.copyPayloadShallow(allocator, Payload.Name), .empty_struct => return self.copyPayloadShallow(allocator, Payload.ContainerScope), .@"struct" => return self.copyPayloadShallow(allocator, Payload.Struct), + .enum_simple => return self.copyPayloadShallow(allocator, Payload.EnumSimple), + .enum_full, .enum_nonexhaustive => return self.copyPayloadShallow(allocator, Payload.EnumFull), .@"opaque" => return self.copyPayloadShallow(allocator, Payload.Opaque), } } @@ -629,8 +637,8 @@ pub const Type = extern union { self: Type, comptime fmt: []const u8, options: std.fmt.FormatOptions, - out_stream: anytype, - ) @TypeOf(out_stream).Error!void { + writer: anytype, + ) @TypeOf(writer).Error!void { comptime assert(fmt.len == 0); var ty = self; while (true) { @@ -670,132 +678,149 @@ pub const Type = extern union { .comptime_float, .noreturn, .var_args_param, - => return out_stream.writeAll(@tagName(t)), - - .enum_literal => return out_stream.writeAll("@Type(.EnumLiteral)"), - .@"null" => return out_stream.writeAll("@Type(.Null)"), - .@"undefined" => return out_stream.writeAll("@Type(.Undefined)"), - - .empty_struct, .empty_struct_literal => return out_stream.writeAll("struct {}"), - .@"struct" => return out_stream.writeAll("(struct)"), - .anyerror_void_error_union => return out_stream.writeAll("anyerror!void"), - .const_slice_u8 => return out_stream.writeAll("[]const u8"), - .fn_noreturn_no_args => return out_stream.writeAll("fn() noreturn"), - .fn_void_no_args => return out_stream.writeAll("fn() void"), - .fn_naked_noreturn_no_args => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), - .fn_ccc_void_no_args => return out_stream.writeAll("fn() callconv(.C) void"), - .single_const_pointer_to_comptime_int => return out_stream.writeAll("*const comptime_int"), + => return writer.writeAll(@tagName(t)), + + .enum_literal => return writer.writeAll("@Type(.EnumLiteral)"), + .@"null" => return writer.writeAll("@Type(.Null)"), + .@"undefined" => return writer.writeAll("@Type(.Undefined)"), + + .empty_struct, .empty_struct_literal => return writer.writeAll("struct {}"), + + .@"struct" => { + const struct_obj = self.castTag(.@"struct").?.data; + return struct_obj.owner_decl.renderFullyQualifiedName(writer); + }, + .enum_full, .enum_nonexhaustive => { + const enum_full = self.castTag(.enum_full).?.data; + return enum_full.owner_decl.renderFullyQualifiedName(writer); + }, + .enum_simple => { + const enum_simple = self.castTag(.enum_simple).?.data; + return enum_simple.owner_decl.renderFullyQualifiedName(writer); + }, + .@"opaque" => { + // TODO use declaration name + return writer.writeAll("opaque {}"); + }, + + .anyerror_void_error_union => return writer.writeAll("anyerror!void"), + .const_slice_u8 => return writer.writeAll("[]const u8"), + .fn_noreturn_no_args => return writer.writeAll("fn() noreturn"), + .fn_void_no_args => return writer.writeAll("fn() void"), + .fn_naked_noreturn_no_args => return writer.writeAll("fn() callconv(.Naked) noreturn"), + .fn_ccc_void_no_args => return writer.writeAll("fn() callconv(.C) void"), + .single_const_pointer_to_comptime_int => return writer.writeAll("*const comptime_int"), .function => { const payload = ty.castTag(.function).?.data; - try out_stream.writeAll("fn("); + try writer.writeAll("fn("); for (payload.param_types) |param_type, i| { - if (i != 0) try out_stream.writeAll(", "); - try param_type.format("", .{}, out_stream); + if (i != 0) try writer.writeAll(", "); + try param_type.format("", .{}, writer); } if (payload.is_var_args) { if (payload.param_types.len != 0) { - try out_stream.writeAll(", "); + try writer.writeAll(", "); } - try out_stream.writeAll("..."); + try writer.writeAll("..."); } - try out_stream.writeAll(") callconv(."); - try out_stream.writeAll(@tagName(payload.cc)); - try out_stream.writeAll(")"); + try writer.writeAll(") callconv(."); + try writer.writeAll(@tagName(payload.cc)); + try writer.writeAll(")"); ty = payload.return_type; continue; }, .array_u8 => { const len = ty.castTag(.array_u8).?.data; - return out_stream.print("[{d}]u8", .{len}); + return writer.print("[{d}]u8", .{len}); }, .array_u8_sentinel_0 => { const len = ty.castTag(.array_u8_sentinel_0).?.data; - return out_stream.print("[{d}:0]u8", .{len}); + return writer.print("[{d}:0]u8", .{len}); }, .array => { const payload = ty.castTag(.array).?.data; - try out_stream.print("[{d}]", .{payload.len}); + try writer.print("[{d}]", .{payload.len}); ty = payload.elem_type; continue; }, .array_sentinel => { const payload = ty.castTag(.array_sentinel).?.data; - try out_stream.print("[{d}:{}]", .{ payload.len, payload.sentinel }); + try writer.print("[{d}:{}]", .{ payload.len, payload.sentinel }); ty = payload.elem_type; continue; }, .single_const_pointer => { const pointee_type = ty.castTag(.single_const_pointer).?.data; - try out_stream.writeAll("*const "); + try writer.writeAll("*const "); ty = pointee_type; continue; }, .single_mut_pointer => { const pointee_type = ty.castTag(.single_mut_pointer).?.data; - try out_stream.writeAll("*"); + try writer.writeAll("*"); ty = pointee_type; continue; }, .many_const_pointer => { const pointee_type = ty.castTag(.many_const_pointer).?.data; - try out_stream.writeAll("[*]const "); + try writer.writeAll("[*]const "); ty = pointee_type; continue; }, .many_mut_pointer => { const pointee_type = ty.castTag(.many_mut_pointer).?.data; - try out_stream.writeAll("[*]"); + try writer.writeAll("[*]"); ty = pointee_type; continue; }, .c_const_pointer => { const pointee_type = ty.castTag(.c_const_pointer).?.data; - try out_stream.writeAll("[*c]const "); + try writer.writeAll("[*c]const "); ty = pointee_type; continue; }, .c_mut_pointer => { const pointee_type = ty.castTag(.c_mut_pointer).?.data; - try out_stream.writeAll("[*c]"); + try writer.writeAll("[*c]"); ty = pointee_type; continue; }, .const_slice => { const pointee_type = ty.castTag(.const_slice).?.data; - try out_stream.writeAll("[]const "); + try writer.writeAll("[]const "); ty = pointee_type; continue; }, .mut_slice => { const pointee_type = ty.castTag(.mut_slice).?.data; - try out_stream.writeAll("[]"); + try writer.writeAll("[]"); ty = pointee_type; continue; }, .int_signed => { const bits = ty.castTag(.int_signed).?.data; - return out_stream.print("i{d}", .{bits}); + return writer.print("i{d}", .{bits}); }, .int_unsigned => { const bits = ty.castTag(.int_unsigned).?.data; - return out_stream.print("u{d}", .{bits}); + return writer.print("u{d}", .{bits}); }, .optional => { const child_type = ty.castTag(.optional).?.data; - try out_stream.writeByte('?'); + try writer.writeByte('?'); ty = child_type; continue; }, .optional_single_const_pointer => { const pointee_type = ty.castTag(.optional_single_const_pointer).?.data; - try out_stream.writeAll("?*const "); + try writer.writeAll("?*const "); ty = pointee_type; continue; }, .optional_single_mut_pointer => { const pointee_type = ty.castTag(.optional_single_mut_pointer).?.data; - try out_stream.writeAll("?*"); + try writer.writeAll("?*"); ty = pointee_type; continue; }, @@ -804,48 +829,46 @@ pub const Type = extern union { const payload = ty.castTag(.pointer).?.data; if (payload.sentinel) |some| switch (payload.size) { .One, .C => unreachable, - .Many => try out_stream.print("[*:{}]", .{some}), - .Slice => try out_stream.print("[:{}]", .{some}), + .Many => try writer.print("[*:{}]", .{some}), + .Slice => try writer.print("[:{}]", .{some}), } else switch (payload.size) { - .One => try out_stream.writeAll("*"), - .Many => try out_stream.writeAll("[*]"), - .C => try out_stream.writeAll("[*c]"), - .Slice => try out_stream.writeAll("[]"), + .One => try writer.writeAll("*"), + .Many => try writer.writeAll("[*]"), + .C => try writer.writeAll("[*c]"), + .Slice => try writer.writeAll("[]"), } if (payload.@"align" != 0) { - try out_stream.print("align({d}", .{payload.@"align"}); + try writer.print("align({d}", .{payload.@"align"}); if (payload.bit_offset != 0) { - try out_stream.print(":{d}:{d}", .{ payload.bit_offset, payload.host_size }); + try writer.print(":{d}:{d}", .{ payload.bit_offset, payload.host_size }); } - try out_stream.writeAll(") "); + try writer.writeAll(") "); } - if (!payload.mutable) try out_stream.writeAll("const "); - if (payload.@"volatile") try out_stream.writeAll("volatile "); - if (payload.@"allowzero") try out_stream.writeAll("allowzero "); + if (!payload.mutable) try writer.writeAll("const "); + if (payload.@"volatile") try writer.writeAll("volatile "); + if (payload.@"allowzero") try writer.writeAll("allowzero "); ty = payload.pointee_type; continue; }, .error_union => { const payload = ty.castTag(.error_union).?.data; - try payload.error_set.format("", .{}, out_stream); - try out_stream.writeAll("!"); + try payload.error_set.format("", .{}, writer); + try writer.writeAll("!"); ty = payload.payload; continue; }, .error_set => { const error_set = ty.castTag(.error_set).?.data; - return out_stream.writeAll(std.mem.spanZ(error_set.owner_decl.name)); + return writer.writeAll(std.mem.spanZ(error_set.owner_decl.name)); }, .error_set_single => { const name = ty.castTag(.error_set_single).?.data; - return out_stream.print("error{{{s}}}", .{name}); + return writer.print("error{{{s}}}", .{name}); }, - .inferred_alloc_const => return out_stream.writeAll("(inferred_alloc_const)"), - .inferred_alloc_mut => return out_stream.writeAll("(inferred_alloc_mut)"), - // TODO use declaration name - .@"opaque" => return out_stream.writeAll("opaque {}"), + .inferred_alloc_const => return writer.writeAll("(inferred_alloc_const)"), + .inferred_alloc_mut => return writer.writeAll("(inferred_alloc_mut)"), } unreachable; } @@ -954,6 +977,19 @@ pub const Type = extern union { return false; } }, + .enum_full => { + const enum_full = self.castTag(.enum_full).?.data; + return enum_full.fields.count() >= 2; + }, + .enum_simple => { + const enum_simple = self.castTag(.enum_simple).?.data; + return enum_simple.fields.count() >= 2; + }, + .enum_nonexhaustive => { + var buffer: Payload.Bits = undefined; + const int_tag_ty = self.intTagType(&buffer); + return int_tag_ty.hasCodeGenBits(); + }, // TODO lazy types .array => self.elemType().hasCodeGenBits() and self.arrayLen() != 0, @@ -1112,13 +1148,37 @@ pub const Type = extern union { } else if (!payload.payload.hasCodeGenBits()) { return payload.error_set.abiAlignment(target); } - @panic("TODO abiAlignment error union"); + return std.math.max( + payload.payload.abiAlignment(target), + payload.error_set.abiAlignment(target), + ); }, .@"struct" => { - @panic("TODO abiAlignment struct"); + // TODO take into account field alignment + // also make this possible to fail, and lazy + // I think we need to move all the functions from type.zig which can + // fail into Sema. + // Probably will need to introduce multi-stage struct resolution just + // like we have in stage1. + const struct_obj = self.castTag(.@"struct").?.data; + var biggest: u32 = 0; + for (struct_obj.fields.entries.items) |entry| { + const field_ty = entry.value.ty; + if (!field_ty.hasCodeGenBits()) continue; + const field_align = field_ty.abiAlignment(target); + if (field_align > biggest) { + return field_align; + } + } + assert(biggest != 0); + return biggest; + }, + .enum_full, .enum_nonexhaustive, .enum_simple => { + var buffer: Payload.Bits = undefined; + const int_tag_ty = self.intTagType(&buffer); + return int_tag_ty.abiAlignment(target); }, - .c_void, .void, .type, @@ -1166,6 +1226,11 @@ pub const Type = extern union { .@"struct" => { @panic("TODO abiSize struct"); }, + .enum_simple, .enum_full, .enum_nonexhaustive => { + var buffer: Payload.Bits = undefined; + const int_tag_ty = self.intTagType(&buffer); + return int_tag_ty.abiSize(target); + }, .u8, .i8, @@ -1276,76 +1341,25 @@ pub const Type = extern union { }; } + /// Asserts the type is an enum. + pub fn intTagType(self: Type, buffer: *Payload.Bits) Type { + switch (self.tag()) { + .enum_full, .enum_nonexhaustive => return self.castTag(.enum_full).?.data.tag_ty, + .enum_simple => { + const enum_simple = self.castTag(.enum_simple).?.data; + const bits = std.math.log2_int_ceil(usize, enum_simple.fields.count()); + buffer.* = .{ + .base = .{ .tag = .int_unsigned }, + .data = bits, + }; + return Type.initPayload(&buffer.base); + }, + else => unreachable, + } + } + pub fn isSinglePointer(self: Type) bool { return switch (self.tag()) { - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .f16, - .f32, - .f64, - .f128, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .const_slice_u8, - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .int_unsigned, - .int_signed, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .@"opaque", - .var_args_param, - => false, - .single_const_pointer, .single_mut_pointer, .single_const_pointer_to_comptime_int, @@ -1354,73 +1368,14 @@ pub const Type = extern union { => true, .pointer => self.castTag(.pointer).?.data.size == .One, + + else => false, }; } /// Asserts the `Type` is a pointer. pub fn ptrSize(self: Type) std.builtin.TypeInfo.Pointer.Size { return switch (self.tag()) { - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .f16, - .f32, - .f64, - .f128, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .int_unsigned, - .int_signed, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .empty_struct, - .empty_struct_literal, - .@"opaque", - .@"struct", - .var_args_param, - => unreachable, - .const_slice, .mut_slice, .const_slice_u8, @@ -1442,159 +1397,26 @@ pub const Type = extern union { => .One, .pointer => self.castTag(.pointer).?.data.size, + + else => unreachable, }; } pub fn isSlice(self: Type) bool { return switch (self.tag()) { - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .f16, - .f32, - .f64, - .f128, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .single_const_pointer_to_comptime_int, - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .int_unsigned, - .int_signed, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"struct", - .@"opaque", - .var_args_param, - => false, - .const_slice, .mut_slice, .const_slice_u8, => true, .pointer => self.castTag(.pointer).?.data.size == .Slice, + + else => false, }; } pub fn isConstPtr(self: Type) bool { return switch (self.tag()) { - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .f16, - .f32, - .f64, - .f128, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .int_unsigned, - .int_signed, - .single_mut_pointer, - .many_mut_pointer, - .c_mut_pointer, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .mut_slice, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"struct", - .@"opaque", - .var_args_param, - => false, - .single_const_pointer, .many_const_pointer, .c_const_pointer, @@ -1604,170 +1426,40 @@ pub const Type = extern union { => true, .pointer => !self.castTag(.pointer).?.data.mutable, + + else => false, }; } pub fn isVolatilePtr(self: Type) bool { return switch (self.tag()) { - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .f16, - .f32, - .f64, - .f128, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .int_unsigned, - .int_signed, - .single_mut_pointer, - .single_const_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"struct", - .@"opaque", - .var_args_param, - => false, - .pointer => { const payload = self.castTag(.pointer).?.data; return payload.@"volatile"; }, + else => false, }; } pub fn isAllowzeroPtr(self: Type) bool { return switch (self.tag()) { - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .f16, - .f32, - .f64, - .f128, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .int_unsigned, - .int_signed, - .single_mut_pointer, - .single_const_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"struct", - .@"opaque", - .var_args_param, - => false, - .pointer => { const payload = self.castTag(.pointer).?.data; return payload.@"allowzero"; }, + else => false, + }; + } + + pub fn isCPtr(self: Type) bool { + return switch (self.tag()) { + .c_const_pointer, + .c_mut_pointer, + => return true, + + .pointer => self.castTag(.pointer).?.data.size == .C, + + else => return false, }; } @@ -1833,64 +1525,6 @@ pub const Type = extern union { /// Asserts the type is a pointer or array type. pub fn elemType(self: Type) Type { return switch (self.tag()) { - .u8 => unreachable, - .i8 => unreachable, - .u16 => unreachable, - .i16 => unreachable, - .u32 => unreachable, - .i32 => unreachable, - .u64 => unreachable, - .i64 => unreachable, - .u128 => unreachable, - .i128 => unreachable, - .usize => unreachable, - .isize => unreachable, - .c_short => unreachable, - .c_ushort => unreachable, - .c_int => unreachable, - .c_uint => unreachable, - .c_long => unreachable, - .c_ulong => unreachable, - .c_longlong => unreachable, - .c_ulonglong => unreachable, - .c_longdouble => unreachable, - .f16 => unreachable, - .f32 => unreachable, - .f64 => unreachable, - .f128 => unreachable, - .c_void => unreachable, - .bool => unreachable, - .void => unreachable, - .type => unreachable, - .anyerror => unreachable, - .comptime_int => unreachable, - .comptime_float => unreachable, - .noreturn => unreachable, - .@"null" => unreachable, - .@"undefined" => unreachable, - .fn_noreturn_no_args => unreachable, - .fn_void_no_args => unreachable, - .fn_naked_noreturn_no_args => unreachable, - .fn_ccc_void_no_args => unreachable, - .function => unreachable, - .int_unsigned => unreachable, - .int_signed => unreachable, - .optional => unreachable, - .optional_single_const_pointer => unreachable, - .optional_single_mut_pointer => unreachable, - .enum_literal => unreachable, - .error_union => unreachable, - .anyerror_void_error_union => unreachable, - .error_set => unreachable, - .error_set_single => unreachable, - .@"struct" => unreachable, - .empty_struct => unreachable, - .empty_struct_literal => unreachable, - .inferred_alloc_const => unreachable, - .inferred_alloc_mut => unreachable, - .@"opaque" => unreachable, - .var_args_param => unreachable, - .array => self.castTag(.array).?.data.elem_type, .array_sentinel => self.castTag(.array_sentinel).?.data.elem_type, .single_const_pointer, @@ -1902,9 +1536,12 @@ pub const Type = extern union { .const_slice, .mut_slice, => self.castPointer().?.data, + .array_u8, .array_u8_sentinel_0, .const_slice_u8 => Type.initTag(.u8), .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int), .pointer => self.castTag(.pointer).?.data.pointee_type, + + else => unreachable, }; } @@ -1972,148 +1609,18 @@ pub const Type = extern union { /// Asserts the type is an array or vector. pub fn arrayLen(self: Type) u64 { return switch (self.tag()) { - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .f16, - .f32, - .f64, - .f128, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .pointer, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .int_unsigned, - .int_signed, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => unreachable, - .array => self.castTag(.array).?.data.len, .array_sentinel => self.castTag(.array_sentinel).?.data.len, .array_u8 => self.castTag(.array_u8).?.data, .array_u8_sentinel_0 => self.castTag(.array_u8_sentinel_0).?.data, + + else => unreachable, }; } /// Asserts the type is an array, pointer or vector. pub fn sentinel(self: Type) ?Value { return switch (self.tag()) { - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .f16, - .f32, - .f64, - .f128, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .const_slice, - .mut_slice, - .const_slice_u8, - .int_unsigned, - .int_signed, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => unreachable, - .single_const_pointer, .single_mut_pointer, .many_const_pointer, @@ -2128,6 +1635,8 @@ pub const Type = extern union { .pointer => return self.castTag(.pointer).?.data.sentinel, .array_sentinel => return self.castTag(.array_sentinel).?.data.sentinel, .array_u8_sentinel_0 => return Value.initTag(.zero), + + else => unreachable, }; } @@ -2139,68 +1648,6 @@ pub const Type = extern union { /// Returns true if and only if the type is a fixed-width, signed integer. pub fn isSignedInt(self: Type) bool { return switch (self.tag()) { - .f16, - .f32, - .f64, - .f128, - .c_longdouble, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .pointer, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .int_unsigned, - .u8, - .usize, - .c_ushort, - .c_uint, - .c_ulong, - .c_ulonglong, - .u16, - .u32, - .u64, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => false, - .int_signed, .i8, .isize, @@ -2211,79 +1658,16 @@ pub const Type = extern union { .i16, .i32, .i64, - .u128, .i128, => true, + + else => false, }; } /// Returns true if and only if the type is a fixed-width, unsigned integer. pub fn isUnsignedInt(self: Type) bool { return switch (self.tag()) { - .f16, - .f32, - .f64, - .f128, - .c_longdouble, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .pointer, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .int_signed, - .i8, - .isize, - .c_short, - .c_int, - .c_long, - .c_longlong, - .i16, - .i32, - .i64, - .u128, - .i128, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => false, - .int_unsigned, .u8, .usize, @@ -2294,65 +1678,16 @@ pub const Type = extern union { .u16, .u32, .u64, + .u128, => true, + + else => false, }; } /// Asserts the type is an integer. pub fn intInfo(self: Type, target: Target) struct { signedness: std.builtin.Signedness, bits: u16 } { return switch (self.tag()) { - .f16, - .f32, - .f64, - .f128, - .c_longdouble, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .pointer, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => unreachable, - .int_unsigned => .{ .signedness = .unsigned, .bits = self.castTag(.int_unsigned).?.data, @@ -2381,75 +1716,13 @@ pub const Type = extern union { .c_ulong => .{ .signedness = .unsigned, .bits = CType.ulong.sizeInBits(target) }, .c_longlong => .{ .signedness = .signed, .bits = CType.longlong.sizeInBits(target) }, .c_ulonglong => .{ .signedness = .unsigned, .bits = CType.ulonglong.sizeInBits(target) }, + + else => unreachable, }; } pub fn isNamedInt(self: Type) bool { return switch (self.tag()) { - .f16, - .f32, - .f64, - .f128, - .c_longdouble, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .pointer, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .int_unsigned, - .int_signed, - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => false, - .usize, .isize, .c_short, @@ -2461,6 +1734,8 @@ pub const Type = extern union { .c_longlong, .c_ulonglong, => true, + + else => false, }; } @@ -2499,74 +1774,7 @@ pub const Type = extern union { .fn_ccc_void_no_args => 0, .function => self.castTag(.function).?.data.param_types.len, - .f16, - .f32, - .f64, - .f128, - .c_longdouble, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .pointer, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .int_unsigned, - .int_signed, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => unreachable, + else => unreachable, }; } @@ -2583,74 +1791,7 @@ pub const Type = extern union { std.mem.copy(Type, types, payload.param_types); }, - .f16, - .f32, - .f64, - .f128, - .c_longdouble, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .pointer, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .int_unsigned, - .int_signed, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => unreachable, + else => unreachable, } } @@ -2662,321 +1803,49 @@ pub const Type = extern union { return payload.param_types[index]; }, - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .f16, - .f32, - .f64, - .f128, - .c_longdouble, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .pointer, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .int_unsigned, - .int_signed, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => unreachable, - } - } - - /// Asserts the type is a function. - pub fn fnReturnType(self: Type) Type { - return switch (self.tag()) { - .fn_noreturn_no_args => Type.initTag(.noreturn), - .fn_naked_noreturn_no_args => Type.initTag(.noreturn), - + else => unreachable, + } + } + + /// Asserts the type is a function. + pub fn fnReturnType(self: Type) Type { + return switch (self.tag()) { + .fn_noreturn_no_args => Type.initTag(.noreturn), + .fn_naked_noreturn_no_args => Type.initTag(.noreturn), + .fn_void_no_args, .fn_ccc_void_no_args, => Type.initTag(.void), .function => self.castTag(.function).?.data.return_type, - - .f16, - .f32, - .f64, - .f128, - .c_longdouble, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .pointer, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .int_unsigned, - .int_signed, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => unreachable, - }; - } - - /// Asserts the type is a function. - pub fn fnCallingConvention(self: Type) std.builtin.CallingConvention { - return switch (self.tag()) { - .fn_noreturn_no_args => .Unspecified, - .fn_void_no_args => .Unspecified, - .fn_naked_noreturn_no_args => .Naked, - .fn_ccc_void_no_args => .C, - .function => self.castTag(.function).?.data.cc, - - .f16, - .f32, - .f64, - .f128, - .c_longdouble, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .pointer, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .int_unsigned, - .int_signed, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => unreachable, - }; - } - - /// Asserts the type is a function. - pub fn fnIsVarArgs(self: Type) bool { - return switch (self.tag()) { - .fn_noreturn_no_args => false, - .fn_void_no_args => false, - .fn_naked_noreturn_no_args => false, - .fn_ccc_void_no_args => false, - .function => self.castTag(.function).?.data.is_var_args, - - .f16, - .f32, - .f64, - .f128, - .c_longdouble, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .pointer, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .int_unsigned, - .int_signed, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => unreachable, + + else => unreachable, + }; + } + + /// Asserts the type is a function. + pub fn fnCallingConvention(self: Type) std.builtin.CallingConvention { + return switch (self.tag()) { + .fn_noreturn_no_args => .Unspecified, + .fn_void_no_args => .Unspecified, + .fn_naked_noreturn_no_args => .Naked, + .fn_ccc_void_no_args => .C, + .function => self.castTag(.function).?.data.cc, + + else => unreachable, + }; + } + + /// Asserts the type is a function. + pub fn fnIsVarArgs(self: Type) bool { + return switch (self.tag()) { + .fn_noreturn_no_args => false, + .fn_void_no_args => false, + .fn_naked_noreturn_no_args => false, + .fn_ccc_void_no_args => false, + .function => self.castTag(.function).?.data.is_var_args, + + else => unreachable, }; } @@ -3013,50 +1882,7 @@ pub const Type = extern union { .int_signed, => true, - .c_void, - .bool, - .void, - .type, - .anyerror, - .noreturn, - .@"null", - .@"undefined", - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .pointer, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .c_const_pointer, - .c_mut_pointer, - .const_slice, - .mut_slice, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => false, + else => false, }; } @@ -3127,6 +1953,23 @@ pub const Type = extern union { } return Value.initTag(.empty_struct_value); }, + .enum_full => { + const enum_full = self.castTag(.enum_full).?.data; + if (enum_full.fields.count() == 1) { + return enum_full.values.entries.items[0].key; + } else { + return null; + } + }, + .enum_simple => { + const enum_simple = self.castTag(.enum_simple).?.data; + if (enum_simple.fields.count() == 1) { + return Value.initTag(.zero); + } else { + return null; + } + }, + .enum_nonexhaustive => return self.castTag(.enum_full).?.data.tag_ty.onePossibleValue(), .empty_struct, .empty_struct_literal => return Value.initTag(.empty_struct_value), .void => return Value.initTag(.void_value), @@ -3166,87 +2009,6 @@ pub const Type = extern union { }; } - pub fn isCPtr(self: Type) bool { - return switch (self.tag()) { - .f16, - .f32, - .f64, - .f128, - .c_longdouble, - .comptime_int, - .comptime_float, - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .bool, - .type, - .anyerror, - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .c_void, - .void, - .noreturn, - .@"null", - .@"undefined", - .int_unsigned, - .int_signed, - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .const_slice, - .mut_slice, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .@"struct", - .empty_struct, - .empty_struct_literal, - .inferred_alloc_const, - .inferred_alloc_mut, - .@"opaque", - .var_args_param, - => return false, - - .c_const_pointer, - .c_mut_pointer, - => return true, - - .pointer => self.castTag(.pointer).?.data.size == .C, - }; - } - pub fn isIndexable(self: Type) bool { const zig_tag = self.zigTypeTag(); // TODO tuples are indexable @@ -3257,80 +2019,12 @@ pub const Type = extern union { /// Asserts that the type is a container. (note: ErrorSet is not a container). pub fn getContainerScope(self: Type) *Module.Scope.Container { return switch (self.tag()) { - .f16, - .f32, - .f64, - .f128, - .c_longdouble, - .comptime_int, - .comptime_float, - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .u128, - .i128, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .bool, - .type, - .anyerror, - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .single_const_pointer_to_comptime_int, - .const_slice_u8, - .c_void, - .void, - .noreturn, - .@"null", - .@"undefined", - .int_unsigned, - .int_signed, - .array, - .array_sentinel, - .array_u8, - .array_u8_sentinel_0, - .single_const_pointer, - .single_mut_pointer, - .many_const_pointer, - .many_mut_pointer, - .const_slice, - .mut_slice, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - .enum_literal, - .error_union, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .c_const_pointer, - .c_mut_pointer, - .pointer, - .inferred_alloc_const, - .inferred_alloc_mut, - .var_args_param, - .empty_struct_literal, - => unreachable, - .@"struct" => &self.castTag(.@"struct").?.data.container, + .enum_full => &self.castTag(.enum_full).?.data.container, .empty_struct => self.castTag(.empty_struct).?.data, .@"opaque" => &self.castTag(.@"opaque").?.data, + + else => unreachable, }; } @@ -3390,7 +2084,43 @@ pub const Type = extern union { } pub fn isExhaustiveEnum(ty: Type) bool { - return false; // TODO + return switch (ty.tag()) { + .enum_full, .enum_simple => true, + else => false, + }; + } + + /// Asserts the type is an enum. + pub fn enumHasInt(ty: Type, int: Value, target: Target) bool { + const S = struct { + fn intInRange(int_val: Value, end: usize) bool { + if (int_val.compareWithZero(.lt)) return false; + var end_payload: Value.Payload.U64 = .{ + .base = .{ .tag = .int_u64 }, + .data = end, + }; + const end_val = Value.initPayload(&end_payload.base); + if (int_val.compare(.gte, end_val)) return false; + return true; + } + }; + switch (ty.tag()) { + .enum_nonexhaustive => return int.intFitsInType(ty, target), + .enum_full => { + const enum_full = ty.castTag(.enum_full).?.data; + if (enum_full.values.count() == 0) { + return S.intInRange(int, enum_full.fields.count()); + } else { + return enum_full.values.contains(int); + } + }, + .enum_simple => { + const enum_simple = ty.castTag(.enum_simple).?.data; + return S.intInRange(int, enum_simple.fields.count()); + }, + + else => unreachable, + } } /// This enum does not directly correspond to `std.builtin.TypeId` because @@ -3482,6 +2212,9 @@ pub const Type = extern union { empty_struct, @"opaque", @"struct", + enum_simple, + enum_full, + enum_nonexhaustive, pub const last_no_payload_tag = Tag.inferred_alloc_const; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -3568,6 +2301,8 @@ pub const Type = extern union { .error_set_single => Payload.Name, .@"opaque" => Payload.Opaque, .@"struct" => Payload.Struct, + .enum_full, .enum_nonexhaustive => Payload.EnumFull, + .enum_simple => Payload.EnumSimple, .empty_struct => Payload.ContainerScope, }; } @@ -3705,6 +2440,16 @@ pub const Type = extern union { base: Payload = .{ .tag = .@"struct" }, data: *Module.Struct, }; + + pub const EnumFull = struct { + base: Payload, + data: *Module.EnumFull, + }; + + pub const EnumSimple = struct { + base: Payload = .{ .tag = .enum_simple }, + data: *Module.EnumSimple, + }; }; }; diff --git a/src/value.zig b/src/value.zig index 4170b005bf..66a23692c1 100644 --- a/src/value.zig +++ b/src/value.zig @@ -103,6 +103,8 @@ pub const Value = extern union { float_64, float_128, enum_literal, + /// A specific enum tag, indicated by the field index (declaration order). + enum_field_index, @"error", error_union, /// This is a special value that tracks a set of types that have been stored @@ -186,6 +188,8 @@ pub const Value = extern union { .enum_literal, => Payload.Bytes, + .enum_field_index => Payload.U32, + .ty => Payload.Ty, .int_type => Payload.IntType, .int_u64 => Payload.U64, @@ -394,6 +398,7 @@ pub const Value = extern union { }; return Value{ .ptr_otherwise = &new_payload.base }; }, + .enum_field_index => return self.copyPayloadShallow(allocator, Payload.U32), .@"error" => return self.copyPayloadShallow(allocator, Payload.Error), .error_union => { const payload = self.castTag(.error_union).?; @@ -416,6 +421,8 @@ pub const Value = extern union { return Value{ .ptr_otherwise = &new_payload.base }; } + /// TODO this should become a debug dump() function. In order to print values in a meaningful way + /// we also need access to the type. pub fn format( self: Value, comptime fmt: []const u8, @@ -506,6 +513,7 @@ pub const Value = extern union { }, .empty_array => return out_stream.writeAll(".{}"), .enum_literal => return out_stream.print(".{}", .{std.zig.fmtId(self.castTag(.enum_literal).?.data)}), + .enum_field_index => return out_stream.print("(enum field {d})", .{self.castTag(.enum_field_index).?.data}), .bytes => return out_stream.print("\"{}\"", .{std.zig.fmtEscapes(self.castTag(.bytes).?.data)}), .repeated => { try out_stream.writeAll("(repeated) "); @@ -626,6 +634,7 @@ pub const Value = extern union { .float_64, .float_128, .enum_literal, + .enum_field_index, .@"error", .error_union, .empty_struct_value, @@ -638,76 +647,6 @@ pub const Value = extern union { /// Asserts the value is an integer. pub fn toBigInt(self: Value, space: *BigIntSpace) BigIntConst { switch (self.tag()) { - .ty, - .int_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .const_slice_u8_type, - .enum_literal_type, - .null_value, - .function, - .extern_fn, - .variable, - .ref_val, - .decl_ref, - .elem_ptr, - .bytes, - .repeated, - .float_16, - .float_32, - .float_64, - .float_128, - .void_value, - .unreachable_value, - .empty_array, - .enum_literal, - .error_union, - .@"error", - .empty_struct_value, - .inferred_alloc, - .abi_align_default, - => unreachable, - - .undef => unreachable, - .zero, .bool_false, => return BigIntMutable.init(&space.limbs, 0).toConst(), @@ -720,82 +659,15 @@ pub const Value = extern union { .int_i64 => return BigIntMutable.init(&space.limbs, self.castTag(.int_i64).?.data).toConst(), .int_big_positive => return self.castTag(.int_big_positive).?.asBigInt(), .int_big_negative => return self.castTag(.int_big_negative).?.asBigInt(), + + .undef => unreachable, + else => unreachable, } } /// Asserts the value is an integer and it fits in a u64 pub fn toUnsignedInt(self: Value) u64 { switch (self.tag()) { - .ty, - .int_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .const_slice_u8_type, - .enum_literal_type, - .null_value, - .function, - .extern_fn, - .variable, - .ref_val, - .decl_ref, - .elem_ptr, - .bytes, - .repeated, - .float_16, - .float_32, - .float_64, - .float_128, - .void_value, - .unreachable_value, - .empty_array, - .enum_literal, - .@"error", - .error_union, - .empty_struct_value, - .inferred_alloc, - .abi_align_default, - => unreachable, - - .undef => unreachable, - .zero, .bool_false, => return 0, @@ -808,82 +680,15 @@ pub const Value = extern union { .int_i64 => return @intCast(u64, self.castTag(.int_i64).?.data), .int_big_positive => return self.castTag(.int_big_positive).?.asBigInt().to(u64) catch unreachable, .int_big_negative => return self.castTag(.int_big_negative).?.asBigInt().to(u64) catch unreachable, + + .undef => unreachable, + else => unreachable, } } /// Asserts the value is an integer and it fits in a i64 pub fn toSignedInt(self: Value) i64 { switch (self.tag()) { - .ty, - .int_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .const_slice_u8_type, - .enum_literal_type, - .null_value, - .function, - .extern_fn, - .variable, - .ref_val, - .decl_ref, - .elem_ptr, - .bytes, - .repeated, - .float_16, - .float_32, - .float_64, - .float_128, - .void_value, - .unreachable_value, - .empty_array, - .enum_literal, - .@"error", - .error_union, - .empty_struct_value, - .inferred_alloc, - .abi_align_default, - => unreachable, - - .undef => unreachable, - .zero, .bool_false, => return 0, @@ -896,6 +701,9 @@ pub const Value = extern union { .int_i64 => return self.castTag(.int_i64).?.data, .int_big_positive => return self.castTag(.int_big_positive).?.asBigInt().to(i64) catch unreachable, .int_big_negative => return self.castTag(.int_big_negative).?.asBigInt().to(i64) catch unreachable, + + .undef => unreachable, + else => unreachable, } } @@ -929,75 +737,6 @@ pub const Value = extern union { /// Returns the number of bits the value requires to represent stored in twos complement form. pub fn intBitCountTwosComp(self: Value) usize { switch (self.tag()) { - .ty, - .int_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .const_slice_u8_type, - .enum_literal_type, - .null_value, - .function, - .extern_fn, - .variable, - .ref_val, - .decl_ref, - .elem_ptr, - .bytes, - .undef, - .repeated, - .float_16, - .float_32, - .float_64, - .float_128, - .void_value, - .unreachable_value, - .empty_array, - .enum_literal, - .@"error", - .error_union, - .empty_struct_value, - .inferred_alloc, - .abi_align_default, - => unreachable, - .zero, .bool_false, => return 0, @@ -1016,80 +755,14 @@ pub const Value = extern union { }, .int_big_positive => return self.castTag(.int_big_positive).?.asBigInt().bitCountTwosComp(), .int_big_negative => return self.castTag(.int_big_negative).?.asBigInt().bitCountTwosComp(), + + else => unreachable, } } /// Asserts the value is an integer, and the destination type is ComptimeInt or Int. pub fn intFitsInType(self: Value, ty: Type, target: Target) bool { switch (self.tag()) { - .ty, - .int_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .const_slice_u8_type, - .enum_literal_type, - .null_value, - .function, - .extern_fn, - .variable, - .ref_val, - .decl_ref, - .elem_ptr, - .bytes, - .repeated, - .float_16, - .float_32, - .float_64, - .float_128, - .void_value, - .unreachable_value, - .empty_array, - .enum_literal, - .@"error", - .error_union, - .empty_struct_value, - .inferred_alloc, - .abi_align_default, - => unreachable, - .zero, .undef, .bool_false, @@ -1144,6 +817,8 @@ pub const Value = extern union { .ComptimeInt => return true, else => unreachable, }, + + else => unreachable, } } @@ -1180,77 +855,6 @@ pub const Value = extern union { /// Asserts the value is a float pub fn floatHasFraction(self: Value) bool { return switch (self.tag()) { - .ty, - .int_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .const_slice_u8_type, - .enum_literal_type, - .bool_true, - .bool_false, - .null_value, - .function, - .extern_fn, - .variable, - .ref_val, - .decl_ref, - .elem_ptr, - .bytes, - .repeated, - .undef, - .int_u64, - .int_i64, - .int_big_positive, - .int_big_negative, - .empty_array, - .void_value, - .unreachable_value, - .enum_literal, - .@"error", - .error_union, - .empty_struct_value, - .inferred_alloc, - .abi_align_default, - => unreachable, - .zero, .one, => false, @@ -1260,76 +864,13 @@ pub const Value = extern union { .float_64 => @rem(self.castTag(.float_64).?.data, 1) != 0, // .float_128 => @rem(self.castTag(.float_128).?.data, 1) != 0, .float_128 => @panic("TODO lld: error: undefined symbol: fmodl"), + + else => unreachable, }; } pub fn orderAgainstZero(lhs: Value) std.math.Order { return switch (lhs.tag()) { - .ty, - .int_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .const_slice_u8_type, - .enum_literal_type, - .null_value, - .function, - .extern_fn, - .variable, - .ref_val, - .decl_ref, - .elem_ptr, - .bytes, - .repeated, - .undef, - .void_value, - .unreachable_value, - .empty_array, - .enum_literal, - .@"error", - .error_union, - .empty_struct_value, - .inferred_alloc, - .abi_align_default, - => unreachable, - .zero, .bool_false, => .eq, @@ -1347,6 +888,8 @@ pub const Value = extern union { .float_32 => std.math.order(lhs.castTag(.float_32).?.data, 0), .float_64 => std.math.order(lhs.castTag(.float_64).?.data, 0), .float_128 => std.math.order(lhs.castTag(.float_128).?.data, 0), + + else => unreachable, }; } @@ -1396,10 +939,12 @@ pub const Value = extern union { } pub fn eql(a: Value, b: Value) bool { - if (a.tag() == b.tag()) { - if (a.tag() == .void_value or a.tag() == .null_value) { + const a_tag = a.tag(); + const b_tag = b.tag(); + if (a_tag == b_tag) { + if (a_tag == .void_value or a_tag == .null_value) { return true; - } else if (a.tag() == .enum_literal) { + } else if (a_tag == .enum_literal) { const a_name = a.castTag(.enum_literal).?.data; const b_name = b.castTag(.enum_literal).?.data; return std.mem.eql(u8, a_name, b_name); @@ -1416,6 +961,10 @@ pub const Value = extern union { return compare(a, .eq, b); } + pub fn hash_u32(self: Value) u32 { + return @truncate(u32, self.hash()); + } + pub fn hash(self: Value) u64 { var hasher = std.hash.Wyhash.init(0); @@ -1493,11 +1042,18 @@ pub const Value = extern union { .zero, .bool_false => std.hash.autoHash(&hasher, @as(u64, 0)), .one, .bool_true => std.hash.autoHash(&hasher, @as(u64, 1)), - .float_16, .float_32, .float_64, .float_128 => {}, + .float_16, .float_32, .float_64, .float_128 => { + @panic("TODO implement Value.hash for floats"); + }, + .enum_literal => { const payload = self.castTag(.enum_literal).?; hasher.update(payload.data); }, + .enum_field_index => { + const payload = self.castTag(.enum_field_index).?; + std.hash.autoHash(&hasher, payload.data); + }, .bytes => { const payload = self.castTag(.bytes).?; hasher.update(payload.data); @@ -1573,80 +1129,6 @@ pub const Value = extern union { /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis. pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value { return switch (self.tag()) { - .ty, - .int_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .const_slice_u8_type, - .enum_literal_type, - .zero, - .one, - .bool_true, - .bool_false, - .null_value, - .function, - .extern_fn, - .variable, - .int_u64, - .int_i64, - .int_big_positive, - .int_big_negative, - .bytes, - .undef, - .repeated, - .float_16, - .float_32, - .float_64, - .float_128, - .void_value, - .unreachable_value, - .empty_array, - .enum_literal, - .@"error", - .error_union, - .empty_struct_value, - .inferred_alloc, - .abi_align_default, - => unreachable, - .ref_val => self.castTag(.ref_val).?.data, .decl_ref => self.castTag(.decl_ref).?.data.value(), .elem_ptr => { @@ -1654,6 +1136,8 @@ pub const Value = extern union { const array_val = try elem_ptr.array_ptr.pointerDeref(allocator); return array_val.elemValue(allocator, elem_ptr.index); }, + + else => unreachable, }; } @@ -1661,86 +1145,14 @@ pub const Value = extern union { /// or an unknown-length pointer, and returns the element value at the index. pub fn elemValue(self: Value, allocator: *Allocator, index: usize) error{OutOfMemory}!Value { switch (self.tag()) { - .ty, - .int_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .const_slice_u8_type, - .enum_literal_type, - .zero, - .one, - .bool_true, - .bool_false, - .null_value, - .function, - .extern_fn, - .variable, - .int_u64, - .int_i64, - .int_big_positive, - .int_big_negative, - .undef, - .elem_ptr, - .ref_val, - .decl_ref, - .float_16, - .float_32, - .float_64, - .float_128, - .void_value, - .unreachable_value, - .enum_literal, - .@"error", - .error_union, - .empty_struct_value, - .inferred_alloc, - .abi_align_default, - => unreachable, - .empty_array => unreachable, // out of bounds array index .bytes => return Tag.int_u64.create(allocator, self.castTag(.bytes).?.data[index]), // No matter the index; all the elements are the same! .repeated => return self.castTag(.repeated).?.data, + + else => unreachable, } } @@ -1766,161 +1178,18 @@ pub const Value = extern union { /// Valid for all types. Asserts the value is not undefined and not unreachable. pub fn isNull(self: Value) bool { return switch (self.tag()) { - .ty, - .int_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .const_slice_u8_type, - .enum_literal_type, - .zero, - .one, - .empty_array, - .bool_true, - .bool_false, - .function, - .extern_fn, - .variable, - .int_u64, - .int_i64, - .int_big_positive, - .int_big_negative, - .ref_val, - .decl_ref, - .elem_ptr, - .bytes, - .repeated, - .float_16, - .float_32, - .float_64, - .float_128, - .void_value, - .enum_literal, - .@"error", - .error_union, - .empty_struct_value, - .abi_align_default, - => false, - .undef => unreachable, .unreachable_value => unreachable, .inferred_alloc => unreachable, .null_value => true, + + else => false, }; } /// Valid for all types. Asserts the value is not undefined and not unreachable. pub fn getError(self: Value) ?[]const u8 { return switch (self.tag()) { - .ty, - .int_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .const_slice_u8_type, - .enum_literal_type, - .zero, - .one, - .null_value, - .empty_array, - .bool_true, - .bool_false, - .function, - .extern_fn, - .variable, - .int_u64, - .int_i64, - .int_big_positive, - .int_big_negative, - .ref_val, - .decl_ref, - .elem_ptr, - .bytes, - .repeated, - .float_16, - .float_32, - .float_64, - .float_128, - .void_value, - .enum_literal, - .empty_struct_value, - .abi_align_default, - => null, - .error_union => { const data = self.castTag(.error_union).?.data; return if (data.tag() == .@"error") @@ -1932,6 +1201,8 @@ pub const Value = extern union { .undef => unreachable, .unreachable_value => unreachable, .inferred_alloc => unreachable, + + else => null, }; } /// Valid for all types. Asserts the value is not undefined. @@ -2021,6 +1292,7 @@ pub const Value = extern union { .float_128, .void_value, .enum_literal, + .enum_field_index, .@"error", .error_union, .empty_struct_value, @@ -2038,6 +1310,11 @@ pub const Value = extern union { pub const Payload = struct { tag: Tag, + pub const U32 = struct { + base: Payload, + data: u32, + }; + pub const U64 = struct { base: Payload, data: u64, diff --git a/src/zir.zig b/src/zir.zig index c70ef17fcd..e63d36bced 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -37,8 +37,6 @@ pub const Code = struct { string_bytes: []u8, /// The meaning of this data is determined by `Inst.Tag` value. extra: []u32, - /// Used for decl_val and decl_ref instructions. - decls: []*Module.Decl, /// Returns the requested data, as well as the new index which is at the start of the /// trailers for the object. @@ -78,7 +76,6 @@ pub const Code = struct { code.instructions.deinit(gpa); gpa.free(code.string_bytes); gpa.free(code.extra); - gpa.free(code.decls); code.* = undefined; } @@ -267,9 +264,6 @@ pub const Inst = struct { /// only the taken branch is analyzed. The then block and else block must /// terminate with an "inline" variant of a noreturn instruction. condbr_inline, - /// A comptime known value. - /// Uses the `const` union field. - @"const", /// A struct type definition. Contains references to ZIR instructions for /// the field types, defaults, and alignments. /// Uses the `pl_node` union field. Payload is `StructDecl`. @@ -286,6 +280,8 @@ pub const Inst = struct { /// the field value expressions and optional type tag expression. /// Uses the `pl_node` union field. Payload is `EnumDecl`. enum_decl, + /// Same as `enum_decl`, except the enum is non-exhaustive. + enum_decl_nonexhaustive, /// An opaque type definition. Provides an AST node only. /// Uses the `node` union field. opaque_decl, @@ -369,6 +365,11 @@ pub const Inst = struct { import, /// Integer literal that fits in a u64. Uses the int union value. int, + /// A float literal that fits in a f32. Uses the float union value. + float, + /// A float literal that fits in a f128. Uses the `pl_node` union value. + /// Payload is `Float128`. + float128, /// Convert an integer value to another integer type, asserting that the destination type /// can hold the same mathematical value. /// Uses the `pl_node` field. AST is the `@intCast` syntax. @@ -667,6 +668,12 @@ pub const Inst = struct { /// A struct literal with a specified type, with no fields. /// Uses the `un_node` field. struct_init_empty, + /// Converts an integer into an enum value. + /// Uses `pl_node` with payload `Bin`. `lhs` is enum type, `rhs` is operand. + int_to_enum, + /// Converts an enum value into an integer. Resulting type will be the tag type + /// of the enum. Uses `un_node`. + enum_to_int, /// Returns whether the instruction is one of the control flow "noreturn" types. /// Function calls do not count. @@ -712,12 +719,12 @@ pub const Inst = struct { .cmp_gt, .cmp_neq, .coerce_result_ptr, - .@"const", .struct_decl, .struct_decl_packed, .struct_decl_extern, .union_decl, .enum_decl, + .enum_decl_nonexhaustive, .opaque_decl, .dbg_stmt_node, .decl_ref, @@ -740,6 +747,8 @@ pub const Inst = struct { .fn_type_cc, .fn_type_cc_var_args, .int, + .float, + .float128, .intcast, .int_type, .is_non_null, @@ -822,6 +831,8 @@ pub const Inst = struct { .switch_block_ref_under_multi, .validate_struct_init_ptr, .struct_init_empty, + .int_to_enum, + .enum_to_int, => false, .@"break", @@ -1184,7 +1195,6 @@ pub const Inst = struct { } }, bin: Bin, - @"const": *TypedValue, /// For strings which may contain null bytes. str: struct { /// Offset into `string_bytes`. @@ -1226,6 +1236,16 @@ pub const Inst = struct { /// Offset from Decl AST node index. node: i32, int: u64, + float: struct { + /// Offset from Decl AST node index. + /// `Tag` determines which kind of AST node this points to. + src_node: i32, + number: f32, + + pub fn src(self: @This()) LazySrcLoc { + return .{ .node_offset = self.src_node }; + } + }, array_type_sentinel: struct { len: Ref, /// index into extra, points to an `ArrayTypeSentinel` @@ -1507,6 +1527,22 @@ pub const Inst = struct { tag_type: Ref, fields_len: u32, }; + + /// A f128 value, broken up into 4 u32 parts. + pub const Float128 = struct { + piece0: u32, + piece1: u32, + piece2: u32, + piece3: u32, + + pub fn get(self: Float128) f128 { + const int_bits = @as(u128, self.piece0) | + (@as(u128, self.piece1) << 32) | + (@as(u128, self.piece2) << 64) | + (@as(u128, self.piece3) << 96); + return @bitCast(f128, int_bits); + } + }; }; pub const SpecialProng = enum { none, @"else", under }; @@ -1581,6 +1617,7 @@ const Writer = struct { .typeof, .typeof_elem, .struct_init_empty, + .enum_to_int, => try self.writeUnNode(stream, inst), .ref, @@ -1594,11 +1631,12 @@ const Writer = struct { => try self.writeBoolBr(stream, inst), .array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst), - .@"const" => try self.writeConst(stream, inst), .param_type => try self.writeParamType(stream, inst), .ptr_type_simple => try self.writePtrTypeSimple(stream, inst), .ptr_type => try self.writePtrType(stream, inst), .int => try self.writeInt(stream, inst), + .float => try self.writeFloat(stream, inst), + .float128 => try self.writeFloat128(stream, inst), .str => try self.writeStr(stream, inst), .elided => try stream.writeAll(")"), .int_type => try self.writeIntType(stream, inst), @@ -1619,6 +1657,7 @@ const Writer = struct { .slice_sentinel, .union_decl, .enum_decl, + .enum_decl_nonexhaustive, => try self.writePlNode(stream, inst), .add, @@ -1647,6 +1686,7 @@ const Writer = struct { .merge_error_sets, .bit_and, .bit_or, + .int_to_enum, => try self.writePlNodeBin(stream, inst), .call, @@ -1773,15 +1813,6 @@ const Writer = struct { try stream.writeAll("TODO)"); } - fn writeConst( - self: *Writer, - stream: anytype, - inst: Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const inst_data = self.code.instructions.items(.data)[inst].@"const"; - try stream.writeAll("TODO)"); - } - fn writeParamType( self: *Writer, stream: anytype, @@ -1819,6 +1850,23 @@ const Writer = struct { try stream.print("{d})", .{inst_data}); } + fn writeFloat(self: *Writer, stream: anytype, inst: Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].float; + const src = inst_data.src(); + try stream.print("{d}) ", .{inst_data.number}); + try self.writeSrc(stream, src); + } + + fn writeFloat128(self: *Writer, stream: anytype, inst: Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Inst.Float128, inst_data.payload_index).data; + const src = inst_data.src(); + const number = extra.get(); + // TODO improve std.format to be able to print f128 values + try stream.print("{d}) ", .{@floatCast(f64, number)}); + try self.writeSrc(stream, src); + } + fn writeStr( self: *Writer, stream: anytype, @@ -2136,7 +2184,8 @@ const Writer = struct { fn writePlNodeDecl(self: *Writer, stream: anytype, inst: Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const decl = self.code.decls[inst_data.payload_index]; + const owner_decl = self.scope.ownerDecl().?; + const decl = owner_decl.dependencies.entries.items[inst_data.payload_index].key; try stream.print("{s}) ", .{decl.name}); try self.writeSrc(stream, inst_data.src()); } -- cgit v1.2.3 From acf9151008d5a97e5d91b34cc29f73af79062b48 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 6 Apr 2021 22:36:28 -0700 Subject: stage2: implement field access for `Enum.tag` syntax --- src/Sema.zig | 104 +++++++++++++++++++++++++++++++++++++++++++---------------- src/type.zig | 26 +++++++-------- src/zir.zig | 2 +- 3 files changed, 90 insertions(+), 42 deletions(-) (limited to 'src/type.zig') diff --git a/src/Sema.zig b/src/Sema.zig index 771da877b4..8ff71ce8f2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4351,22 +4351,25 @@ fn namedFieldPtr( field_name: []const u8, field_name_src: LazySrcLoc, ) InnerError!*Inst { + const mod = sema.mod; + const arena = sema.arena; + const elem_ty = switch (object_ptr.ty.zigTypeTag()) { .Pointer => object_ptr.ty.elemType(), - else => return sema.mod.fail(&block.base, object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}), + else => return mod.fail(&block.base, object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}), }; switch (elem_ty.zigTypeTag()) { .Array => { if (mem.eql(u8, field_name, "len")) { - return sema.mod.constInst(sema.arena, src, .{ + return mod.constInst(arena, src, .{ .ty = Type.initTag(.single_const_pointer_to_comptime_int), .val = try Value.Tag.ref_val.create( - sema.arena, - try Value.Tag.int_u64.create(sema.arena, elem_ty.arrayLen()), + arena, + try Value.Tag.int_u64.create(arena, elem_ty.arrayLen()), ), }); } else { - return sema.mod.fail( + return mod.fail( &block.base, field_name_src, "no member named '{s}' in '{}'", @@ -4379,15 +4382,15 @@ fn namedFieldPtr( switch (ptr_child.zigTypeTag()) { .Array => { if (mem.eql(u8, field_name, "len")) { - return sema.mod.constInst(sema.arena, src, .{ + return mod.constInst(arena, src, .{ .ty = Type.initTag(.single_const_pointer_to_comptime_int), .val = try Value.Tag.ref_val.create( - sema.arena, - try Value.Tag.int_u64.create(sema.arena, ptr_child.arrayLen()), + arena, + try Value.Tag.int_u64.create(arena, ptr_child.arrayLen()), ), }); } else { - return sema.mod.fail( + return mod.fail( &block.base, field_name_src, "no member named '{s}' in '{}'", @@ -4402,7 +4405,7 @@ fn namedFieldPtr( _ = try sema.resolveConstValue(block, object_ptr.src, object_ptr); const result = try sema.analyzeLoad(block, src, object_ptr, object_ptr.src); const val = result.value().?; - const child_type = try val.toType(sema.arena); + const child_type = try val.toType(arena); switch (child_type.zigTypeTag()) { .ErrorSet => { // TODO resolve inferred error sets @@ -4416,42 +4419,87 @@ fn namedFieldPtr( break :blk name; } } - return sema.mod.fail(&block.base, src, "no error named '{s}' in '{}'", .{ + return mod.fail(&block.base, src, "no error named '{s}' in '{}'", .{ field_name, child_type, }); - } else (try sema.mod.getErrorValue(field_name)).key; + } else (try mod.getErrorValue(field_name)).key; - return sema.mod.constInst(sema.arena, src, .{ - .ty = try sema.mod.simplePtrType(sema.arena, child_type, false, .One), + return mod.constInst(arena, src, .{ + .ty = try mod.simplePtrType(arena, child_type, false, .One), .val = try Value.Tag.ref_val.create( - sema.arena, - try Value.Tag.@"error".create(sema.arena, .{ + arena, + try Value.Tag.@"error".create(arena, .{ .name = name, }), ), }); }, - .Struct => { - const container_scope = child_type.getContainerScope(); - if (sema.mod.lookupDeclName(&container_scope.base, field_name)) |decl| { - // TODO if !decl.is_pub and inDifferentFiles() "{} is private" - return sema.analyzeDeclRef(block, src, decl); - } + .Struct, .Opaque, .Union => { + if (child_type.getContainerScope()) |container_scope| { + if (mod.lookupDeclName(&container_scope.base, field_name)) |decl| { + // TODO if !decl.is_pub and inDifferentFiles() "{} is private" + return sema.analyzeDeclRef(block, src, decl); + } - if (container_scope.file_scope == sema.mod.root_scope) { - return sema.mod.fail(&block.base, src, "root source file has no member called '{s}'", .{field_name}); - } else { - return sema.mod.fail(&block.base, src, "container '{}' has no member called '{s}'", .{ child_type, field_name }); + // TODO this will give false positives for structs inside the root file + if (container_scope.file_scope == mod.root_scope) { + return mod.fail( + &block.base, + src, + "root source file has no member named '{s}'", + .{field_name}, + ); + } + } + // TODO add note: declared here + const kw_name = switch (child_type.zigTypeTag()) { + .Struct => "struct", + .Opaque => "opaque", + .Union => "union", + else => unreachable, + }; + return mod.fail(&block.base, src, "{s} '{}' has no member named '{s}'", .{ + kw_name, child_type, field_name, + }); + }, + .Enum => { + if (child_type.getContainerScope()) |container_scope| { + if (mod.lookupDeclName(&container_scope.base, field_name)) |decl| { + // TODO if !decl.is_pub and inDifferentFiles() "{} is private" + return sema.analyzeDeclRef(block, src, decl); + } } + const maybe_field_index: ?usize = switch (child_type.tag()) { + .enum_full, .enum_nonexhaustive => blk: { + const enum_full = child_type.castTag(.enum_full).?.data; + break :blk enum_full.fields.getIndex(field_name); + }, + .enum_simple => blk: { + const enum_simple = child_type.castTag(.enum_simple).?.data; + break :blk enum_simple.fields.getIndex(field_name); + }, + else => unreachable, + }; + const field_index = maybe_field_index orelse { + return mod.fail(&block.base, src, "enum '{}' has no member named '{s}'", .{ + child_type, field_name, + }); + }; + const field_index_u32 = @intCast(u32, field_index); + const enum_val = try Value.Tag.enum_field_index.create(arena, field_index_u32); + return mod.constInst(arena, src, .{ + .ty = try mod.simplePtrType(arena, child_type, false, .One), + .val = try Value.Tag.ref_val.create(arena, enum_val), + }); }, - else => return sema.mod.fail(&block.base, src, "type '{}' does not support field access", .{child_type}), + else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}), } }, .Struct => return sema.analyzeStructFieldPtr(block, src, object_ptr, field_name, field_name_src, elem_ty), else => {}, } - return sema.mod.fail(&block.base, src, "type '{}' does not support field access", .{elem_ty}); + return mod.fail(&block.base, src, "type '{}' does not support field access", .{elem_ty}); } fn analyzeStructFieldPtr( diff --git a/src/type.zig b/src/type.zig index c338072c15..576dc46e7e 100644 --- a/src/type.zig +++ b/src/type.zig @@ -634,13 +634,13 @@ pub const Type = extern union { } pub fn format( - self: Type, + start_type: Type, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { comptime assert(fmt.len == 0); - var ty = self; + var ty = start_type; while (true) { const t = ty.tag(); switch (t) { @@ -687,15 +687,15 @@ pub const Type = extern union { .empty_struct, .empty_struct_literal => return writer.writeAll("struct {}"), .@"struct" => { - const struct_obj = self.castTag(.@"struct").?.data; + const struct_obj = ty.castTag(.@"struct").?.data; return struct_obj.owner_decl.renderFullyQualifiedName(writer); }, .enum_full, .enum_nonexhaustive => { - const enum_full = self.castTag(.enum_full).?.data; + const enum_full = ty.castTag(.enum_full).?.data; return enum_full.owner_decl.renderFullyQualifiedName(writer); }, .enum_simple => { - const enum_simple = self.castTag(.enum_simple).?.data; + const enum_simple = ty.castTag(.enum_simple).?.data; return enum_simple.owner_decl.renderFullyQualifiedName(writer); }, .@"opaque" => { @@ -1886,8 +1886,8 @@ pub const Type = extern union { }; } - pub fn onePossibleValue(self: Type) ?Value { - var ty = self; + pub fn onePossibleValue(starting_type: Type) ?Value { + var ty = starting_type; while (true) switch (ty.tag()) { .f16, .f32, @@ -1954,7 +1954,7 @@ pub const Type = extern union { return Value.initTag(.empty_struct_value); }, .enum_full => { - const enum_full = self.castTag(.enum_full).?.data; + const enum_full = ty.castTag(.enum_full).?.data; if (enum_full.fields.count() == 1) { return enum_full.values.entries.items[0].key; } else { @@ -1962,14 +1962,14 @@ pub const Type = extern union { } }, .enum_simple => { - const enum_simple = self.castTag(.enum_simple).?.data; + const enum_simple = ty.castTag(.enum_simple).?.data; if (enum_simple.fields.count() == 1) { return Value.initTag(.zero); } else { return null; } }, - .enum_nonexhaustive => return self.castTag(.enum_full).?.data.tag_ty.onePossibleValue(), + .enum_nonexhaustive => ty = ty.castTag(.enum_full).?.data.tag_ty, .empty_struct, .empty_struct_literal => return Value.initTag(.empty_struct_value), .void => return Value.initTag(.void_value), @@ -2016,15 +2016,15 @@ pub const Type = extern union { (self.isSinglePointer() and self.elemType().zigTypeTag() == .Array); } - /// Asserts that the type is a container. (note: ErrorSet is not a container). - pub fn getContainerScope(self: Type) *Module.Scope.Container { + /// Returns null if the type has no container. + pub fn getContainerScope(self: Type) ?*Module.Scope.Container { return switch (self.tag()) { .@"struct" => &self.castTag(.@"struct").?.data.container, .enum_full => &self.castTag(.enum_full).?.data.container, .empty_struct => self.castTag(.empty_struct).?.data, .@"opaque" => &self.castTag(.@"opaque").?.data, - else => unreachable, + else => null, }; } diff --git a/src/zir.zig b/src/zir.zig index e63d36bced..00ff23acb0 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -1572,6 +1572,7 @@ const Writer = struct { .intcast, .store, .store_to_block_ptr, + .store_to_inferred_ptr, => try self.writeBin(stream, inst), .alloc, @@ -1769,7 +1770,6 @@ const Writer = struct { .bitcast, .bitcast_result_ptr, - .store_to_inferred_ptr, => try stream.writeAll("TODO)"), } } -- cgit v1.2.3 From 19cf987198ff4de0b1460263a62ff6c41e1e3915 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 6 Apr 2021 23:19:46 -0700 Subject: C backend: implement Enum types and values They are lowered directly as the integer tag type, with no typedef. --- src/Sema.zig | 19 ++++++++++++++----- src/codegen/c.zig | 37 ++++++++++++++++++++++++++++++++++++- src/type.zig | 6 +++--- 3 files changed, 53 insertions(+), 9 deletions(-) (limited to 'src/type.zig') diff --git a/src/Sema.zig b/src/Sema.zig index 8ff71ce8f2..115ca11858 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1918,11 +1918,20 @@ fn zirEnumToInt(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerErr switch (enum_tag.ty.tag()) { .enum_full => { const enum_full = enum_tag.ty.castTag(.enum_full).?.data; - const val = enum_full.values.entries.items[field_index].key; - return mod.constInst(arena, src, .{ - .ty = int_tag_ty, - .val = val, - }); + if (enum_full.values.count() != 0) { + const val = enum_full.values.entries.items[field_index].key; + return mod.constInst(arena, src, .{ + .ty = int_tag_ty, + .val = val, + }); + } else { + // Field index and integer values are the same. + const val = try Value.Tag.int_u64.create(arena, field_index); + return mod.constInst(arena, src, .{ + .ty = int_tag_ty, + .val = val, + }); + } }, .enum_simple => { // Field index and integer values are the same. diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 876f86ed02..d5d18baa1c 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -172,7 +172,10 @@ pub const DeclGen = struct { val: Value, ) error{ OutOfMemory, AnalysisFail }!void { if (val.isUndef()) { - return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: properly handle undefined in all cases (with debug safety?)", .{}); + // This should lower to 0xaa bytes in safe modes, and for unsafe modes should + // lower to leaving variables uninitialized (that might need to be implemented + // outside of this function). + return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement renderValue undef", .{}); } switch (t.zigTypeTag()) { .Int => { @@ -288,6 +291,31 @@ pub const DeclGen = struct { try writer.writeAll(", .error = 0 }"); } }, + .Enum => { + switch (val.tag()) { + .enum_field_index => { + const field_index = val.castTag(.enum_field_index).?.data; + switch (t.tag()) { + .enum_simple => return writer.print("{d}", .{field_index}), + .enum_full, .enum_nonexhaustive => { + const enum_full = t.cast(Type.Payload.EnumFull).?.data; + if (enum_full.values.count() != 0) { + const tag_val = enum_full.values.entries.items[field_index].key; + return dg.renderValue(writer, enum_full.tag_ty, tag_val); + } else { + return writer.print("{d}", .{field_index}); + } + }, + else => unreachable, + } + }, + else => { + var int_tag_ty_buffer: Type.Payload.Bits = undefined; + const int_tag_ty = t.intTagType(&int_tag_ty_buffer); + return dg.renderValue(writer, int_tag_ty, val); + }, + } + }, else => |e| return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement value {s}", .{ @tagName(e), }), @@ -472,6 +500,13 @@ pub const DeclGen = struct { try w.writeAll(name); dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered }); }, + .Enum => { + // For enums, we simply use the integer tag type. + var int_tag_ty_buffer: Type.Payload.Bits = undefined; + const int_tag_ty = t.intTagType(&int_tag_ty_buffer); + + try dg.renderType(w, int_tag_ty); + }, .Null, .Undefined => unreachable, // must be const or comptime else => |e| return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type {s}", .{ @tagName(e), diff --git a/src/type.zig b/src/type.zig index 576dc46e7e..6173c0b800 100644 --- a/src/type.zig +++ b/src/type.zig @@ -691,7 +691,7 @@ pub const Type = extern union { return struct_obj.owner_decl.renderFullyQualifiedName(writer); }, .enum_full, .enum_nonexhaustive => { - const enum_full = ty.castTag(.enum_full).?.data; + const enum_full = ty.cast(Payload.EnumFull).?.data; return enum_full.owner_decl.renderFullyQualifiedName(writer); }, .enum_simple => { @@ -1344,7 +1344,7 @@ pub const Type = extern union { /// Asserts the type is an enum. pub fn intTagType(self: Type, buffer: *Payload.Bits) Type { switch (self.tag()) { - .enum_full, .enum_nonexhaustive => return self.castTag(.enum_full).?.data.tag_ty, + .enum_full, .enum_nonexhaustive => return self.cast(Payload.EnumFull).?.data.tag_ty, .enum_simple => { const enum_simple = self.castTag(.enum_simple).?.data; const bits = std.math.log2_int_ceil(usize, enum_simple.fields.count()); @@ -1969,7 +1969,7 @@ pub const Type = extern union { return null; } }, - .enum_nonexhaustive => ty = ty.castTag(.enum_full).?.data.tag_ty, + .enum_nonexhaustive => ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty, .empty_struct, .empty_struct_literal => return Value.initTag(.empty_struct_value), .void => return Value.initTag(.void_value), -- cgit v1.2.3 From 4e8fb9e6a5f9a65bbf6469e386e83ba469e7543b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Apr 2021 11:26:07 -0700 Subject: Sema: DRY up enum field analysis and add "declared here" notes --- src/Module.zig | 21 ++++++++++ src/Sema.zig | 114 ++++++++++++++++++++++----------------------------- src/type.zig | 36 ++++++++++++++++ test/stage2/cbe.zig | 4 +- test/stage2/test.zig | 9 ++-- 5 files changed, 113 insertions(+), 71 deletions(-) (limited to 'src/type.zig') diff --git a/src/Module.zig b/src/Module.zig index 8c58b63995..9d26738e14 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -366,6 +366,13 @@ pub const ErrorSet = struct { /// The string bytes are stored in the owner Decl arena. /// They are in the same order they appear in the AST. names_ptr: [*]const []const u8, + + pub fn srcLoc(self: ErrorSet) SrcLoc { + return .{ + .container = .{ .decl = self.owner_decl }, + .lazy = .{ .node_offset = self.node_offset }, + }; + } }; /// Represents the data that a struct declaration provides. @@ -408,6 +415,13 @@ pub const EnumSimple = struct { fields: std.StringArrayHashMapUnmanaged(void), /// Offset from `owner_decl`, points to the enum decl AST node. node_offset: i32, + + pub fn srcLoc(self: EnumSimple) SrcLoc { + return .{ + .container = .{ .decl = self.owner_decl }, + .lazy = .{ .node_offset = self.node_offset }, + }; + } }; /// Represents the data that an enum declaration provides, when there is @@ -429,6 +443,13 @@ pub const EnumFull = struct { node_offset: i32, pub const ValueMap = std.ArrayHashMapUnmanaged(Value, void, Value.hash_u32, Value.eql, false); + + pub fn srcLoc(self: EnumFull) SrcLoc { + return .{ + .container = .{ .decl = self.owner_decl }, + .lazy = .{ .node_offset = self.node_offset }, + }; + } }; /// Some Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. diff --git a/src/Sema.zig b/src/Sema.zig index 0cd9837c3a..41544cdfe5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -897,7 +897,7 @@ fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Ind try mod.errNoteNonLazy( struct_obj.srcLoc(), msg, - "'{s}' declared here", + "struct '{s}' declared here", .{fqn}, ); return mod.failWithOwnedErrorMsg(&block.base, msg); @@ -925,7 +925,7 @@ fn failWithBadFieldAccess( .{ field_name, fqn }, ); errdefer msg.destroy(gpa); - try mod.errNoteNonLazy(struct_obj.srcLoc(), msg, "'{s}' declared here", .{fqn}); + try mod.errNoteNonLazy(struct_obj.srcLoc(), msg, "struct declared here", .{}); break :msg msg; }; return mod.failWithOwnedErrorMsg(&block.base, msg); @@ -4479,21 +4479,24 @@ fn namedFieldPtr( return sema.analyzeDeclRef(block, src, decl); } } - const maybe_field_index: ?usize = switch (child_type.tag()) { - .enum_full, .enum_nonexhaustive => blk: { - const enum_full = child_type.castTag(.enum_full).?.data; - break :blk enum_full.fields.getIndex(field_name); - }, - .enum_simple => blk: { - const enum_simple = child_type.castTag(.enum_simple).?.data; - break :blk enum_simple.fields.getIndex(field_name); - }, - else => unreachable, - }; - const field_index = maybe_field_index orelse { - return mod.fail(&block.base, src, "enum '{}' has no member named '{s}'", .{ - child_type, field_name, - }); + const field_index = child_type.enumFieldIndex(field_name) orelse { + const msg = msg: { + const msg = try mod.errMsg( + &block.base, + src, + "enum '{}' has no member named '{s}'", + .{ child_type, field_name }, + ); + errdefer msg.destroy(sema.gpa); + try mod.errNoteNonLazy( + child_type.declSrcLoc(), + msg, + "enum declared here", + .{}, + ); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); }; const field_index_u32 = @intCast(u32, field_index); const enum_val = try Value.Tag.enum_field_index.create(arena, field_index_u32); @@ -4593,10 +4596,13 @@ fn coerce( return sema.bitcast(block, dest_type, inst); } + const mod = sema.mod; + const arena = sema.arena; + // undefined to anything if (inst.value()) |val| { if (val.isUndef() or inst.ty.zigTypeTag() == .Undefined) { - return sema.mod.constInst(sema.arena, inst_src, .{ .ty = dest_type, .val = val }); + return mod.constInst(arena, inst_src, .{ .ty = dest_type, .val = val }); } } assert(inst.ty.zigTypeTag() != .Undefined); @@ -4610,13 +4616,13 @@ fn coerce( if (try sema.coerceNum(block, dest_type, inst)) |some| return some; - const target = sema.mod.getTarget(); + const target = mod.getTarget(); switch (dest_type.zigTypeTag()) { .Optional => { // null to ?T if (inst.ty.zigTypeTag() == .Null) { - return sema.mod.constInst(sema.arena, inst_src, .{ .ty = dest_type, .val = Value.initTag(.null_value) }); + return mod.constInst(arena, inst_src, .{ .ty = dest_type, .val = Value.initTag(.null_value) }); } // T to ?T @@ -4703,63 +4709,39 @@ fn coerce( } }, .Enum => { + // enum literal to enum if (inst.ty.zigTypeTag() == .EnumLiteral) { - const val = (try sema.resolveDefinedValue(block, inst_src, inst)).?; + const val = try sema.resolveConstValue(block, inst_src, inst); const bytes = val.castTag(.enum_literal).?.data; - switch (dest_type.tag()) { - .enum_full => { - const enumeration = dest_type.castTag(.enum_full).?.data; - const enum_fields = enumeration.fields; - const i = enum_fields.getIndex(bytes) orelse return sema.mod.fail( + const field_index = dest_type.enumFieldIndex(bytes) orelse { + const msg = msg: { + const msg = try mod.errMsg( &block.base, inst_src, - "enum '{s}' has no field named '{s}'", - .{ enumeration.owner_decl.name, bytes }, + "enum '{}' has no field named '{s}'", + .{ dest_type, bytes }, ); - const val_pl = try Value.Tag.enum_field_index.create(sema.arena, @intCast(u32, i)); - return sema.mod.constInst(sema.arena, inst_src, .{ - .ty = dest_type, - .val = val_pl, - }); - }, - .enum_simple => { - const enumeration = dest_type.castTag(.enum_simple).?.data; - const enum_fields = enumeration.fields; - const i = enum_fields.getIndex(bytes) orelse return sema.mod.fail( - &block.base, - inst_src, - "enum '{s}' has no field named '{s}'", - .{ enumeration.owner_decl.name, bytes }, - ); - const val_pl = try Value.Tag.enum_field_index.create(sema.arena, @intCast(u32, i)); - return sema.mod.constInst(sema.arena, inst_src, .{ - .ty = dest_type, - .val = val_pl, - }); - }, - .enum_nonexhaustive => { - const enumeration = dest_type.castTag(.enum_nonexhaustive).?.data; - const enum_fields = enumeration.fields; - const i = enum_fields.getIndex(bytes) orelse return sema.mod.fail( - &block.base, - inst_src, - "enum '{s}' has no field named '{s}'", - .{ enumeration.owner_decl.name, bytes }, + errdefer msg.destroy(sema.gpa); + try mod.errNoteNonLazy( + dest_type.declSrcLoc(), + msg, + "enum declared here", + .{}, ); - const val_pl = try Value.Tag.enum_field_index.create(sema.arena, @intCast(u32, i)); - return sema.mod.constInst(sema.arena, inst_src, .{ - .ty = dest_type, - .val = val_pl, - }); - }, - else => unreachable, - } + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); + }; + return mod.constInst(arena, inst_src, .{ + .ty = dest_type, + .val = try Value.Tag.enum_field_index.create(arena, @intCast(u32, field_index)), + }); } }, else => {}, } - return sema.mod.fail(&block.base, inst_src, "expected {}, found {}", .{ dest_type, inst.ty }); + return mod.fail(&block.base, inst_src, "expected {}, found {}", .{ dest_type, inst.ty }); } const InMemoryCoercionResult = enum { diff --git a/src/type.zig b/src/type.zig index 6173c0b800..06e84c245b 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2090,6 +2090,42 @@ pub const Type = extern union { }; } + pub fn enumFieldIndex(ty: Type, field_name: []const u8) ?usize { + switch (ty.tag()) { + .enum_full, .enum_nonexhaustive => { + const enum_full = ty.cast(Payload.EnumFull).?.data; + return enum_full.fields.getIndex(field_name); + }, + .enum_simple => { + const enum_simple = ty.castTag(.enum_simple).?.data; + return enum_simple.fields.getIndex(field_name); + }, + else => unreachable, + } + } + + pub fn declSrcLoc(ty: Type) Module.SrcLoc { + switch (ty.tag()) { + .enum_full, .enum_nonexhaustive => { + const enum_full = ty.cast(Payload.EnumFull).?.data; + return enum_full.srcLoc(); + }, + .enum_simple => { + const enum_simple = ty.castTag(.enum_simple).?.data; + return enum_simple.srcLoc(); + }, + .@"struct" => { + const struct_obj = ty.castTag(.@"struct").?.data; + return struct_obj.srcLoc(); + }, + .error_set => { + const error_set = ty.castTag(.error_set).?.data; + return error_set.srcLoc(); + }, + else => unreachable, + } + } + /// Asserts the type is an enum. pub fn enumHasInt(ty: Type, int: Value, target: Target) bool { const S = struct { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index bfab645323..3acbbdd0ae 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -508,7 +508,7 @@ pub fn addCases(ctx: *TestContext) !void { \\} , &.{ ":3:21: error: mising struct field: x", - ":1:15: note: 'Point' declared here", + ":1:15: note: struct 'Point' declared here", }); case.addError( \\const Point = struct { x: i32, y: i32 }; @@ -522,7 +522,7 @@ pub fn addCases(ctx: *TestContext) !void { \\} , &.{ ":6:10: error: no field named 'z' in struct 'Point'", - ":1:15: note: 'Point' declared here", + ":1:15: note: struct declared here", }); case.addCompareOutput( \\const Point = struct { x: i32, y: i32 }; diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 2d3a445ca1..fe0c5e5d9e 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1022,7 +1022,7 @@ pub fn addCases(ctx: *TestContext) !void { "Hello, World!\n", ); try case.files.append(.{ - .src = + .src = \\pub fn print() void { \\ asm volatile ("syscall" \\ : @@ -1621,11 +1621,11 @@ pub fn addCases(ctx: *TestContext) !void { "", ); case.addError( - \\const E = enum { a, b }; \\export fn _start() noreturn { \\ const a: E = .c; \\ exit(); \\} + \\const E = enum { a, b }; \\fn exit() noreturn { \\ asm volatile ("syscall" \\ : @@ -1635,6 +1635,9 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , &.{":3:19: error: enum 'E' has no field named 'c'"}); + , &.{ + ":2:19: error: enum 'E' has no field named 'c'", + ":5:11: note: enum declared here", + }); } } -- cgit v1.2.3 From 18119aae30660c27b088214319cfca396fdf04bf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Apr 2021 12:15:05 -0700 Subject: Sema: implement comparison analysis for non-numeric types --- src/Sema.zig | 41 ++++++++++++++++++++++++++++++++--------- src/type.zig | 41 +++++++++++++++++++++++++++++++++++++++++ test/stage2/cbe.zig | 21 +++++++++++++++++++++ 3 files changed, 94 insertions(+), 9 deletions(-) (limited to 'src/type.zig') diff --git a/src/Sema.zig b/src/Sema.zig index 80b9ae37c9..379d19fd9d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3776,9 +3776,13 @@ fn zirCmp( const tracy = trace(@src()); defer tracy.end(); + const mod = sema.mod; + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data; const src: LazySrcLoc = inst_data.src(); + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; const lhs = try sema.resolveInst(extra.lhs); const rhs = try sema.resolveInst(extra.rhs); @@ -3790,7 +3794,7 @@ fn zirCmp( const rhs_ty_tag = rhs.ty.zigTypeTag(); if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) { // null == null, null != null - return sema.mod.constBool(sema.arena, src, op == .eq); + return mod.constBool(sema.arena, src, op == .eq); } else if (is_equality_cmp and ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or rhs_ty_tag == .Null and lhs_ty_tag == .Optional)) @@ -3801,23 +3805,23 @@ fn zirCmp( } else if (is_equality_cmp and ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr()))) { - return sema.mod.fail(&block.base, src, "TODO implement C pointer cmp", .{}); + return mod.fail(&block.base, src, "TODO implement C pointer cmp", .{}); } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) { const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty; - return sema.mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type}); + return mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type}); } else if (is_equality_cmp and ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union))) { - return sema.mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{}); + return mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{}); } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) { if (!is_equality_cmp) { - return sema.mod.fail(&block.base, src, "{s} operator not allowed for errors", .{@tagName(op)}); + return mod.fail(&block.base, src, "{s} operator not allowed for errors", .{@tagName(op)}); } if (rhs.value()) |rval| { if (lhs.value()) |lval| { // TODO optimisation oppurtunity: evaluate if std.mem.eql is faster with the names, or calling to Module.getErrorValue to get the values and then compare them is faster - return sema.mod.constBool(sema.arena, src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq)); + return mod.constBool(sema.arena, src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq)); } } try sema.requireRuntimeBlock(block, src); @@ -3829,11 +3833,30 @@ fn zirCmp( return sema.cmpNumeric(block, src, lhs, rhs, op); } else if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) { if (!is_equality_cmp) { - return sema.mod.fail(&block.base, src, "{s} operator not allowed for types", .{@tagName(op)}); + return mod.fail(&block.base, src, "{s} operator not allowed for types", .{@tagName(op)}); } - return sema.mod.constBool(sema.arena, src, lhs.value().?.eql(rhs.value().?) == (op == .eq)); + return mod.constBool(sema.arena, src, lhs.value().?.eql(rhs.value().?) == (op == .eq)); + } + + const instructions = &[_]*Inst{ lhs, rhs }; + const resolved_type = try sema.resolvePeerTypes(block, src, instructions); + if (!resolved_type.isSelfComparable(is_equality_cmp)) { + return mod.fail(&block.base, src, "operator not allowed for type '{}'", .{resolved_type}); } - return sema.mod.fail(&block.base, src, "TODO implement more cmp analysis", .{}); + + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); + const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); + try sema.requireRuntimeBlock(block, src); // TODO try to do it at comptime + const bool_type = Type.initTag(.bool); // TODO handle vectors + const tag: Inst.Tag = switch (op) { + .lt => .cmp_lt, + .lte => .cmp_lte, + .eq => .cmp_eq, + .gte => .cmp_gte, + .gt => .cmp_gt, + .neq => .cmp_neq, + }; + return block.addBinOp(src, bool_type, tag, casted_lhs, casted_rhs); } fn zirTypeof(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { diff --git a/src/type.zig b/src/type.zig index 06e84c245b..dfd021617e 100644 --- a/src/type.zig +++ b/src/type.zig @@ -107,6 +107,42 @@ pub const Type = extern union { } } + pub fn isSelfComparable(ty: Type, is_equality_cmp: bool) bool { + return switch (ty.zigTypeTag()) { + .Int, + .Float, + .ComptimeFloat, + .ComptimeInt, + .Vector, // TODO some vectors require is_equality_cmp==true + => true, + + .Bool, + .Type, + .Void, + .ErrorSet, + .Fn, + .BoundFn, + .Opaque, + .AnyFrame, + .Enum, + .EnumLiteral, + => is_equality_cmp, + + .NoReturn, + .Array, + .Struct, + .Undefined, + .Null, + .ErrorUnion, + .Union, + .Frame, + => false, + + .Pointer => is_equality_cmp or ty.isCPtr(), + .Optional => is_equality_cmp and ty.isAbiPtr(), + }; + } + pub fn initTag(comptime small_tag: Tag) Type { comptime assert(@enumToInt(small_tag) < Tag.no_payload_count); return .{ .tag_if_small_enough = @enumToInt(small_tag) }; @@ -1583,6 +1619,11 @@ pub const Type = extern union { } } + /// Returns whether the type is represented as a pointer in the ABI. + pub fn isAbiPtr(self: Type) bool { + @panic("TODO implement this"); + } + /// Asserts that the type is an error union. pub fn errorUnionChild(self: Type) Type { return switch (self.tag()) { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 3acbbdd0ae..870059629b 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -536,6 +536,27 @@ pub fn addCases(ctx: *TestContext) !void { , ""); } + { + var case = ctx.exeFromCompiledC("enums", .{}); + case.addCompareOutput( + \\const Number = enum { One, Two, Three }; + \\ + \\export fn main() c_int { + \\ var number1 = Number.One; + \\ var number2: Number = .Two; + \\ const number3 = @intToEnum(Number, 2); + \\ if (number1 == number2) return 1; + \\ if (number2 == number3) return 1; + \\ if (@enumToInt(number1) != 0) return 1; + \\ if (@enumToInt(number2) != 1) return 1; + \\ if (@enumToInt(number3) != 2) return 1; + \\ var x: Number = .Two; + \\ if (number2 != x) return 1; + \\ return 0; + \\} + , ""); + } + ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { \\ unreachable; -- cgit v1.2.3 From 090834380cff9f2743f9c71e8e141ac5aceb747e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Apr 2021 13:06:21 -0700 Subject: Type: use isPtrLikeOptional instead of isAbiPtr Thanks @Vexu --- src/type.zig | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'src/type.zig') diff --git a/src/type.zig b/src/type.zig index dfd021617e..a542f9a6fa 100644 --- a/src/type.zig +++ b/src/type.zig @@ -139,7 +139,7 @@ pub const Type = extern union { => false, .Pointer => is_equality_cmp or ty.isCPtr(), - .Optional => is_equality_cmp and ty.isAbiPtr(), + .Optional => is_equality_cmp and ty.isPtrLikeOptional(), }; } @@ -1619,11 +1619,6 @@ pub const Type = extern union { } } - /// Returns whether the type is represented as a pointer in the ABI. - pub fn isAbiPtr(self: Type) bool { - @panic("TODO implement this"); - } - /// Asserts that the type is an error union. pub fn errorUnionChild(self: Type) Type { return switch (self.tag()) { -- cgit v1.2.3 From 8f28e26e7a4f770f8d8e700386e2ade111948891 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Apr 2021 16:39:10 -0700 Subject: Sema: implement switch validation for enums --- src/Sema.zig | 193 +++++++++++++++++++++++++++++++++++++++++++++------- src/type.zig | 64 +++++++++++++++++ test/stage2/cbe.zig | 6 +- 3 files changed, 238 insertions(+), 25 deletions(-) (limited to 'src/type.zig') diff --git a/src/Sema.zig b/src/Sema.zig index 379d19fd9d..655fc4bd06 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2729,6 +2729,8 @@ fn analyzeSwitch( src_node_offset: i32, ) InnerError!*Inst { const gpa = sema.gpa; + const mod = sema.mod; + const special: struct { body: []const zir.Inst.Index, end: usize } = switch (special_prong) { .none => .{ .body = &.{}, .end = extra_end }, .under, .@"else" => blk: { @@ -2748,14 +2750,14 @@ fn analyzeSwitch( // Validate usage of '_' prongs. if (special_prong == .under and !operand.ty.isExhaustiveEnum()) { const msg = msg: { - const msg = try sema.mod.errMsg( + const msg = try mod.errMsg( &block.base, src, "'_' prong only allowed when switching on non-exhaustive enums", .{}, ); errdefer msg.destroy(gpa); - try sema.mod.errNote( + try mod.errNote( &block.base, special_prong_src, msg, @@ -2764,14 +2766,121 @@ fn analyzeSwitch( ); break :msg msg; }; - return sema.mod.failWithOwnedErrorMsg(&block.base, msg); + return mod.failWithOwnedErrorMsg(&block.base, msg); } // Validate for duplicate items, missing else prong, and invalid range. switch (operand.ty.zigTypeTag()) { - .Enum => return sema.mod.fail(&block.base, src, "TODO validate switch .Enum", .{}), - .ErrorSet => return sema.mod.fail(&block.base, src, "TODO validate switch .ErrorSet", .{}), - .Union => return sema.mod.fail(&block.base, src, "TODO validate switch .Union", .{}), + .Enum => { + var seen_fields = try gpa.alloc(?AstGen.SwitchProngSrc, operand.ty.enumFieldCount()); + defer gpa.free(seen_fields); + + var extra_index: usize = special.end; + { + var scalar_i: u32 = 0; + while (scalar_i < scalar_cases_len) : (scalar_i += 1) { + const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const body = sema.code.extra[extra_index..][0..body_len]; + extra_index += body_len; + + try sema.validateSwitchItemEnum( + block, + seen_fields, + item_ref, + src_node_offset, + .{ .scalar = scalar_i }, + ); + } + } + { + var multi_i: u32 = 0; + while (multi_i < multi_cases_len) : (multi_i += 1) { + const items_len = sema.code.extra[extra_index]; + extra_index += 1; + const ranges_len = sema.code.extra[extra_index]; + extra_index += 1; + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const items = sema.code.refSlice(extra_index, items_len); + extra_index += items_len + body_len; + + for (items) |item_ref, item_i| { + try sema.validateSwitchItemEnum( + block, + seen_fields, + item_ref, + src_node_offset, + .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } }, + ); + } + + try sema.validateSwitchNoRange(block, ranges_len, operand.ty, src_node_offset); + } + } + const all_tags_handled = for (seen_fields) |seen_src| { + if (seen_src == null) break false; + } else true; + + switch (special_prong) { + .none => { + if (!all_tags_handled) { + const msg = msg: { + const msg = try mod.errMsg( + &block.base, + src, + "switch must handle all possibilities", + .{}, + ); + errdefer msg.destroy(sema.gpa); + try mod.errNoteNonLazy( + operand.ty.declSrcLoc(), + msg, + "enum '{}' declared here", + .{operand.ty}, + ); + for (seen_fields) |seen_src, i| { + if (seen_src != null) continue; + + const field_name = operand.ty.enumFieldName(i); + + // TODO have this point to the tag decl instead of here + try mod.errNote( + &block.base, + src, + msg, + "unhandled enumeration value: '{s}", + .{field_name}, + ); + } + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); + } + }, + .under => { + if (all_tags_handled) return mod.fail( + &block.base, + special_prong_src, + "unreachable '_' prong; all cases already handled", + .{}, + ); + }, + .@"else" => { + if (all_tags_handled) return mod.fail( + &block.base, + special_prong_src, + "unreachable else prong; all cases already handled", + .{}, + ); + }, + } + }, + + .ErrorSet => return mod.fail(&block.base, src, "TODO validate switch .ErrorSet", .{}), + .Union => return mod.fail(&block.base, src, "TODO validate switch .Union", .{}), .Int, .ComptimeInt => { var range_set = RangeSet.init(gpa); defer range_set.deinit(); @@ -2844,11 +2953,11 @@ fn analyzeSwitch( var arena = std.heap.ArenaAllocator.init(gpa); defer arena.deinit(); - const min_int = try operand.ty.minInt(&arena, sema.mod.getTarget()); - const max_int = try operand.ty.maxInt(&arena, sema.mod.getTarget()); + const min_int = try operand.ty.minInt(&arena, mod.getTarget()); + const max_int = try operand.ty.maxInt(&arena, mod.getTarget()); if (try range_set.spans(min_int, max_int)) { if (special_prong == .@"else") { - return sema.mod.fail( + return mod.fail( &block.base, special_prong_src, "unreachable else prong; all cases already handled", @@ -2859,7 +2968,7 @@ fn analyzeSwitch( } } if (special_prong != .@"else") { - return sema.mod.fail( + return mod.fail( &block.base, src, "switch must handle all possibilities", @@ -2922,7 +3031,7 @@ fn analyzeSwitch( switch (special_prong) { .@"else" => { if (true_count + false_count == 2) { - return sema.mod.fail( + return mod.fail( &block.base, src, "unreachable else prong; all cases already handled", @@ -2932,7 +3041,7 @@ fn analyzeSwitch( }, .under, .none => { if (true_count + false_count < 2) { - return sema.mod.fail( + return mod.fail( &block.base, src, "switch must handle all possibilities", @@ -2944,7 +3053,7 @@ fn analyzeSwitch( }, .EnumLiteral, .Void, .Fn, .Pointer, .Type => { if (special_prong != .@"else") { - return sema.mod.fail( + return mod.fail( &block.base, src, "else prong required when switching on type '{}'", @@ -3016,7 +3125,7 @@ fn analyzeSwitch( .AnyFrame, .ComptimeFloat, .Float, - => return sema.mod.fail(&block.base, operand_src, "invalid switch operand type '{}'", .{ + => return mod.fail(&block.base, operand_src, "invalid switch operand type '{}'", .{ operand.ty, }), } @@ -3291,7 +3400,7 @@ fn resolveSwitchItemVal( switch_node_offset: i32, switch_prong_src: AstGen.SwitchProngSrc, range_expand: AstGen.SwitchProngSrc.RangeExpand, -) InnerError!Value { +) InnerError!TypedValue { const item = try sema.resolveInst(item_ref); // We have to avoid the other helper functions here because we cannot construct a LazySrcLoc // because we only have the switch AST node. Only if we know for sure we need to report @@ -3301,7 +3410,7 @@ fn resolveSwitchItemVal( const src = switch_prong_src.resolve(block.src_decl, switch_node_offset, range_expand); return sema.failWithUseOfUndef(block, src); } - return val; + return TypedValue{ .ty = item.ty, .val = val }; } const src = switch_prong_src.resolve(block.src_decl, switch_node_offset, range_expand); return sema.failWithNeededComptime(block, src); @@ -3316,8 +3425,8 @@ fn validateSwitchRange( src_node_offset: i32, switch_prong_src: AstGen.SwitchProngSrc, ) InnerError!void { - const first_val = try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first); - const last_val = try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last); + const first_val = (try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first)).val; + const last_val = (try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last)).val; const maybe_prev_src = try range_set.add(first_val, last_val, switch_prong_src); return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); } @@ -3330,11 +3439,46 @@ fn validateSwitchItem( src_node_offset: i32, switch_prong_src: AstGen.SwitchProngSrc, ) InnerError!void { - const item_val = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none); + const item_val = (try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none)).val; const maybe_prev_src = try range_set.add(item_val, item_val, switch_prong_src); return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); } +fn validateSwitchItemEnum( + sema: *Sema, + block: *Scope.Block, + seen_fields: []?AstGen.SwitchProngSrc, + item_ref: zir.Inst.Ref, + src_node_offset: i32, + switch_prong_src: AstGen.SwitchProngSrc, +) InnerError!void { + const mod = sema.mod; + const item_tv = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none); + const field_index = item_tv.ty.enumTagFieldIndex(item_tv.val) orelse { + const msg = msg: { + const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .none); + const msg = try mod.errMsg( + &block.base, + src, + "enum '{}' has no tag with value '{}'", + .{ item_tv.ty, item_tv.val }, + ); + errdefer msg.destroy(sema.gpa); + try mod.errNoteNonLazy( + item_tv.ty.declSrcLoc(), + msg, + "enum declared here", + .{}, + ); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); + }; + const maybe_prev_src = seen_fields[field_index]; + seen_fields[field_index] = switch_prong_src; + return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); +} + fn validateSwitchDupe( sema: *Sema, block: *Scope.Block, @@ -3343,17 +3487,18 @@ fn validateSwitchDupe( src_node_offset: i32, ) InnerError!void { const prev_prong_src = maybe_prev_src orelse return; + const mod = sema.mod; const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .none); const prev_src = prev_prong_src.resolve(block.src_decl, src_node_offset, .none); const msg = msg: { - const msg = try sema.mod.errMsg( + const msg = try mod.errMsg( &block.base, src, "duplicate switch value", .{}, ); errdefer msg.destroy(sema.gpa); - try sema.mod.errNote( + try mod.errNote( &block.base, prev_src, msg, @@ -3362,7 +3507,7 @@ fn validateSwitchDupe( ); break :msg msg; }; - return sema.mod.failWithOwnedErrorMsg(&block.base, msg); + return mod.failWithOwnedErrorMsg(&block.base, msg); } fn validateSwitchItemBool( @@ -3374,7 +3519,7 @@ fn validateSwitchItemBool( src_node_offset: i32, switch_prong_src: AstGen.SwitchProngSrc, ) InnerError!void { - const item_val = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none); + const item_val = (try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none)).val; if (item_val.toBool()) { true_count.* += 1; } else { @@ -3396,7 +3541,7 @@ fn validateSwitchItemSparse( src_node_offset: i32, switch_prong_src: AstGen.SwitchProngSrc, ) InnerError!void { - const item_val = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none); + const item_val = (try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none)).val; const entry = (try seen_values.fetchPut(item_val, switch_prong_src)) orelse return; return sema.validateSwitchDupe(block, entry.value, switch_prong_src, src_node_offset); } diff --git a/src/type.zig b/src/type.zig index a542f9a6fa..1e52d2eb74 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2126,6 +2126,34 @@ pub const Type = extern union { }; } + pub fn enumFieldCount(ty: Type) usize { + switch (ty.tag()) { + .enum_full, .enum_nonexhaustive => { + const enum_full = ty.cast(Payload.EnumFull).?.data; + return enum_full.fields.count(); + }, + .enum_simple => { + const enum_simple = ty.castTag(.enum_simple).?.data; + return enum_simple.fields.count(); + }, + else => unreachable, + } + } + + pub fn enumFieldName(ty: Type, field_index: usize) []const u8 { + switch (ty.tag()) { + .enum_full, .enum_nonexhaustive => { + const enum_full = ty.cast(Payload.EnumFull).?.data; + return enum_full.fields.entries.items[field_index].key; + }, + .enum_simple => { + const enum_simple = ty.castTag(.enum_simple).?.data; + return enum_simple.fields.entries.items[field_index].key; + }, + else => unreachable, + } + } + pub fn enumFieldIndex(ty: Type, field_name: []const u8) ?usize { switch (ty.tag()) { .enum_full, .enum_nonexhaustive => { @@ -2140,6 +2168,42 @@ pub const Type = extern union { } } + /// Asserts `ty` is an enum. `enum_tag` can either be `enum_field_index` or + /// an integer which represents the enum value. Returns the field index in + /// declaration order, or `null` if `enum_tag` does not match any field. + pub fn enumTagFieldIndex(ty: Type, enum_tag: Value) ?usize { + if (enum_tag.castTag(.enum_field_index)) |payload| { + return @as(usize, payload.data); + } + const S = struct { + fn fieldWithRange(int_val: Value, end: usize) ?usize { + if (int_val.compareWithZero(.lt)) return null; + var end_payload: Value.Payload.U64 = .{ + .base = .{ .tag = .int_u64 }, + .data = end, + }; + const end_val = Value.initPayload(&end_payload.base); + if (int_val.compare(.gte, end_val)) return null; + return int_val.toUnsignedInt(); + } + }; + switch (ty.tag()) { + .enum_full, .enum_nonexhaustive => { + const enum_full = ty.cast(Payload.EnumFull).?.data; + if (enum_full.values.count() == 0) { + return S.fieldWithRange(enum_tag, enum_full.fields.count()); + } else { + return enum_full.values.getIndex(enum_tag); + } + }, + .enum_simple => { + const enum_simple = ty.castTag(.enum_simple).?.data; + return S.fieldWithRange(enum_tag, enum_simple.fields.count()); + }, + else => unreachable, + } + } + pub fn declSrcLoc(ty: Type) Module.SrcLoc { switch (ty.tag()) { .enum_full, .enum_nonexhaustive => { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 870059629b..f84872b6f7 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -552,7 +552,11 @@ pub fn addCases(ctx: *TestContext) !void { \\ if (@enumToInt(number3) != 2) return 1; \\ var x: Number = .Two; \\ if (number2 != x) return 1; - \\ return 0; + \\ switch (x) { + \\ .One => return 1, + \\ .Two => return 0, + \\ number3 => return 2, + \\ } \\} , ""); } -- cgit v1.2.3 From 57aa289fdef543a507d8da039f5b7e7f2762c878 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Apr 2021 22:19:17 -0700 Subject: Sema: fix switch validation '_' prong on wrong type --- src/Sema.zig | 4 ++-- src/type.zig | 4 ++-- test/stage2/cbe.zig | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) (limited to 'src/type.zig') diff --git a/src/Sema.zig b/src/Sema.zig index a0be08ed71..519d5df401 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1972,7 +1972,7 @@ fn zirIntToEnum(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerErr return mod.fail(&block.base, dest_ty_src, "expected enum, found {}", .{dest_ty}); } - if (!dest_ty.isExhaustiveEnum()) { + if (dest_ty.isNonexhaustiveEnum()) { if (operand.value()) |int_val| { return mod.constInst(arena, src, .{ .ty = dest_ty, @@ -2762,7 +2762,7 @@ fn analyzeSwitch( const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset }; // Validate usage of '_' prongs. - if (special_prong == .under and !operand.ty.isExhaustiveEnum()) { + if (special_prong == .under and !operand.ty.isNonexhaustiveEnum()) { const msg = msg: { const msg = try mod.errMsg( &block.base, diff --git a/src/type.zig b/src/type.zig index 1e52d2eb74..ab31991c36 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2119,9 +2119,9 @@ pub const Type = extern union { } } - pub fn isExhaustiveEnum(ty: Type) bool { + pub fn isNonexhaustiveEnum(ty: Type) bool { return switch (ty.tag()) { - .enum_full, .enum_simple => true, + .enum_nonexhaustive => true, else => false, }; } diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 3cf95ab6d6..1f590e2c47 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -717,6 +717,52 @@ pub fn addCases(ctx: *TestContext) !void { ":4:5: note: unhandled enumeration value: 'b'", ":1:11: note: enum 'E' declared here", }); + + case.addError( + \\const E = enum { a, b, c }; + \\export fn foo() void { + \\ var x: E = .a; + \\ switch (x) { + \\ .a => {}, + \\ .b => {}, + \\ .b => {}, + \\ .c => {}, + \\ } + \\} + , &.{ + ":7:10: error: duplicate switch value", + ":6:10: note: previous value here", + }); + + case.addError( + \\const E = enum { a, b, c }; + \\export fn foo() void { + \\ var x: E = .a; + \\ switch (x) { + \\ .a => {}, + \\ .b => {}, + \\ .c => {}, + \\ else => {}, + \\ } + \\} + , &.{ + ":8:14: error: unreachable else prong; all cases already handled", + }); + + case.addError( + \\const E = enum { a, b, c }; + \\export fn foo() void { + \\ var x: E = .a; + \\ switch (x) { + \\ .a => {}, + \\ .b => {}, + \\ _ => {}, + \\ } + \\} + , &.{ + ":4:5: error: '_' prong only allowed when switching on non-exhaustive enums", + ":7:11: note: '_' prong here", + }); } ctx.c("empty start function", linux_x64, -- cgit v1.2.3