aboutsummaryrefslogtreecommitdiff
path: root/src/AstGen.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/AstGen.zig')
-rw-r--r--src/AstGen.zig290
1 files changed, 237 insertions, 53 deletions
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,