diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index f97254f29a..9b3e72e7aa 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -80,6 +80,9 @@ deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, root_name: []u8, keep_source_files_loaded: bool, +/// Error tags and their values, tag names are duped with mod.gpa. +global_error_set: std.StringHashMapUnmanaged(u16) = .{}, + pub const InnerError = error{ OutOfMemory, AnalysisFail }; const WorkItem = union(enum) { @@ -928,6 +931,11 @@ pub fn deinit(self: *Module) void { self.symbol_exports.deinit(gpa); self.root_scope.destroy(gpa); + + for (self.global_error_set.items()) |entry| { + gpa.free(entry.key); + } + self.global_error_set.deinit(gpa); self.* = undefined; } @@ -2072,6 +2080,15 @@ fn createNewDecl( return new_decl; } +/// Get error value for error tag `name`. +pub fn getErrorValue(self: *Module, name: []const u8) !u16 { + const new_val = @intCast(u16, self.global_error_set.items().len); + if (self.global_error_set.get(name)) |some| return some; + + try self.global_error_set.put(self.gpa, try self.gpa.dupe(u8, name), new_val); + return new_val; +} + /// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites. pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { return scope.cast(Scope.Block) orelse diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 32041fbc2b..b77cf97dfc 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -270,6 +270,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .ErrorUnion => return rlWrap(mod, scope, rl, try typeInixOp(mod, scope, node.castTag(.ErrorUnion).?, .error_union_type)), .MergeErrorSets => return rlWrap(mod, scope, rl, try typeInixOp(mod, scope, node.castTag(.MergeErrorSets).?, .merge_error_sets)), .AnyFrameType => return rlWrap(mod, scope, rl, try anyFrameType(mod, scope, node.castTag(.AnyFrameType).?)), + .ErrorSetDecl => return rlWrap(mod, scope, rl, try errorSetDecl(mod, scope, node.castTag(.ErrorSetDecl).?)), .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), .Catch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Catch", .{}), @@ -291,7 +292,6 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}), .ErrorType => return mod.failNode(scope, node, "TODO implement astgen.expr for .ErrorType", .{}), .FnProto => return mod.failNode(scope, node, "TODO implement astgen.expr for .FnProto", .{}), - .ErrorSetDecl => return mod.failNode(scope, node, "TODO implement astgen.expr for .ErrorSetDecl", .{}), .ContainerDecl => return mod.failNode(scope, node, "TODO implement astgen.expr for .ContainerDecl", .{}), .Comptime => return mod.failNode(scope, node, "TODO implement astgen.expr for .Comptime", .{}), .Nosuspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Nosuspend", .{}), @@ -459,7 +459,9 @@ fn varDecl( const tree = scope.tree(); const name_src = tree.token_locs[node.name_token].start; const ident_name = try identifierTokenString(mod, scope, node.name_token); - const init_node = node.getTrailer("init_node").?; + const init_node = node.getTrailer("init_node") orelse + return mod.fail(scope, name_src, "variables must be initialized", .{}); + switch (tree.token_ids[node.mut_token]) { .Keyword_const => { // Depending on the type of AST the initialization expression is, we may need an lvalue @@ -582,11 +584,7 @@ fn addressOf(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerE fn optionalType(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.op_token].start; - const meta_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); - const operand = try expr(mod, scope, .{ .ty = meta_type }, node.rhs); + const operand = try typeExpr(mod, scope, node.rhs); return addZIRUnOp(mod, scope, src, .optional_type, operand); } @@ -611,18 +609,13 @@ fn ptrType(mod: *Module, scope: *Scope, node: *ast.Node.PtrType) InnerError!*zir } fn ptrSliceType(mod: *Module, scope: *Scope, src: usize, ptr_info: *ast.PtrInfo, rhs: *ast.Node, size: std.builtin.TypeInfo.Pointer.Size) InnerError!*zir.Inst { - const meta_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); - const simple = ptr_info.allowzero_token == null and ptr_info.align_info == null and ptr_info.volatile_token == null and ptr_info.sentinel == null; if (simple) { - const child_type = try expr(mod, scope, .{ .ty = meta_type }, rhs); + const child_type = try typeExpr(mod, scope, rhs); const mutable = ptr_info.const_token == null; // TODO stage1 type inference bug const T = zir.Inst.Tag; @@ -650,7 +643,7 @@ fn ptrSliceType(mod: *Module, scope: *Scope, src: usize, ptr_info: *ast.PtrInfo, kw_args.sentinel = try expr(mod, scope, .none, some); } - const child_type = try expr(mod, scope, .{ .ty = meta_type }, rhs); + const child_type = try typeExpr(mod, scope, rhs); if (kw_args.sentinel) |some| { kw_args.sentinel = try addZIRBinOp(mod, scope, some.src, .as, child_type, some); } @@ -661,10 +654,6 @@ fn ptrSliceType(mod: *Module, scope: *Scope, src: usize, ptr_info: *ast.PtrInfo, fn arrayType(mod: *Module, scope: *Scope, node: *ast.Node.ArrayType) !*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.op_token].start; - const meta_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); const usize_type = try addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.usize_type), @@ -672,18 +661,14 @@ fn arrayType(mod: *Module, scope: *Scope, node: *ast.Node.ArrayType) !*zir.Inst // TODO check for [_]T const len = try expr(mod, scope, .{ .ty = usize_type }, node.len_expr); - const child_type = try expr(mod, scope, .{ .ty = meta_type }, node.rhs); + const elem_type = try typeExpr(mod, scope, node.rhs); - return addZIRBinOp(mod, scope, src, .array_type, len, child_type); + return addZIRBinOp(mod, scope, src, .array_type, len, elem_type); } fn arrayTypeSentinel(mod: *Module, scope: *Scope, node: *ast.Node.ArrayTypeSentinel) !*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.op_token].start; - const meta_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); const usize_type = try addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.usize_type), @@ -692,7 +677,7 @@ fn arrayTypeSentinel(mod: *Module, scope: *Scope, node: *ast.Node.ArrayTypeSenti // TODO check for [_]T const len = try expr(mod, scope, .{ .ty = usize_type }, node.len_expr); const sentinel_uncasted = try expr(mod, scope, .none, node.sentinel); - const elem_type = try expr(mod, scope, .{ .ty = meta_type }, node.rhs); + const elem_type = try typeExpr(mod, scope, node.rhs); const sentinel = try addZIRBinOp(mod, scope, src, .as, elem_type, sentinel_uncasted); return addZIRInst(mod, scope, src, zir.Inst.ArrayTypeSentinel, .{ @@ -706,11 +691,7 @@ fn anyFrameType(mod: *Module, scope: *Scope, node: *ast.Node.AnyFrameType) Inner const tree = scope.tree(); const src = tree.token_locs[node.anyframe_token].start; if (node.result) |some| { - const meta_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); - const return_type = try expr(mod, scope, .{ .ty = meta_type}, some.return_type); + const return_type = try typeExpr(mod, scope, some.return_type); return addZIRUnOp(mod, scope, src, .anyframe_type, return_type); } else { return addZIRInstConst(mod, scope, src, .{ @@ -723,12 +704,8 @@ fn anyFrameType(mod: *Module, scope: *Scope, node: *ast.Node.AnyFrameType) Inner fn typeInixOp(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp, op_inst_tag: zir.Inst.Tag) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.op_token].start; - const meta_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); - const error_set = try expr(mod, scope, .{ .ty = meta_type }, node.lhs); - const payload = try expr(mod, scope, .{ .ty = meta_type }, node.rhs); + const error_set = try typeExpr(mod, scope, node.lhs); + const payload = try typeExpr(mod, scope, node.rhs); return addZIRBinOp(mod, scope, src, op_inst_tag, error_set, payload); } @@ -751,6 +728,20 @@ fn unwrapOptional(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Si return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, unwrapped_ptr)); } +fn errorSetDecl(mod: *Module, scope: *Scope, node: *ast.Node.ErrorSetDecl) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.error_token].start; + const decls = node.decls(); + const fields = try scope.arena().alloc([]const u8, decls.len); + + for (decls) |decl, i| { + const tag = decl.castTag(.ErrorTag).?; + fields[i] = try identifierTokenString(mod, scope, tag.name_token); + } + + return addZIRInst(mod, scope, src, zir.Inst.ErrorSet, .{ .fields = fields }, .{}); +} + /// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating. /// OK in theory it could do it without allocating. This implementation allocates when the @"" form is used. fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool { @@ -1517,12 +1508,8 @@ fn simpleCast( try ensureBuiltinParamCount(mod, scope, call, 2); const tree = scope.tree(); const src = tree.token_locs[call.builtin_token].start; - const type_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); const params = call.params(); - const dest_type = try expr(mod, scope, .{ .ty = type_type }, params[0]); + const dest_type = try typeExpr(mod, scope, params[0]); const rhs = try expr(mod, scope, .none, params[1]); const result = try addZIRBinOp(mod, scope, src, inst_tag, dest_type, rhs); return rlWrap(mod, scope, rl, result); @@ -1584,12 +1571,8 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa try ensureBuiltinParamCount(mod, scope, call, 2); const tree = scope.tree(); const src = tree.token_locs[call.builtin_token].start; - const type_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); const params = call.params(); - const dest_type = try expr(mod, scope, .{ .ty = type_type }, params[0]); + const dest_type = try typeExpr(mod, scope, params[0]); switch (rl) { .none => { const operand = try expr(mod, scope, .none, params[1]); diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 7b43c66200..f7917b9d44 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -91,6 +91,7 @@ pub const Value = extern union { float_64, float_128, enum_literal, + error_set, pub const last_no_payload_tag = Tag.bool_false; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -243,6 +244,9 @@ pub const Value = extern union { }; return Value{ .ptr_otherwise = &new_payload.base }; }, + + // memory is managed by the declaration + .error_set => return self, } } @@ -346,6 +350,14 @@ pub const Value = extern union { .float_32 => return out_stream.print("{}", .{val.cast(Payload.Float_32).?.val}), .float_64 => return out_stream.print("{}", .{val.cast(Payload.Float_64).?.val}), .float_128 => return out_stream.print("{}", .{val.cast(Payload.Float_128).?.val}), + .error_set => { + const error_set = val.cast(Payload.ErrorSet).?; + try out_stream.writeAll("error{"); + for (error_set.fields.items()) |entry| { + try out_stream.print("{},", .{entry.value}); + } + return out_stream.writeAll("}"); + }, }; } @@ -437,6 +449,7 @@ pub const Value = extern union { .float_64, .float_128, .enum_literal, + .error_set, => unreachable, }; } @@ -503,6 +516,7 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, => unreachable, .undef => unreachable, @@ -582,6 +596,7 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, => unreachable, .undef => unreachable, @@ -661,6 +676,7 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, => unreachable, .undef => unreachable, @@ -767,6 +783,7 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, => unreachable, .zero, @@ -850,6 +867,7 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, => unreachable, .zero, @@ -1017,6 +1035,7 @@ pub const Value = extern union { .void_value, .unreachable_value, .enum_literal, + .error_set, => unreachable, .zero => false, @@ -1087,6 +1106,7 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, => unreachable, .zero, @@ -1230,6 +1250,7 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, => unreachable, .ref_val => self.cast(Payload.RefVal).?.val, @@ -1310,6 +1331,7 @@ pub const Value = extern union { .void_value, .unreachable_value, .enum_literal, + .error_set, => unreachable, .empty_array => unreachable, // out of bounds array index @@ -1407,6 +1429,7 @@ pub const Value = extern union { .float_128, .void_value, .enum_literal, + .error_set, => false, .undef => unreachable, @@ -1536,6 +1559,13 @@ pub const Value = extern union { base: Payload = .{ .tag = .float_128 }, val: f128, }; + + pub const ErrorSet = struct { + base: Payload = .{ .tag = .error_set }, + + // TODO revisit this when we have the concept of the error tag type + fields: std.StringHashMapUnmanaged(u16), + }; }; /// Big enough to fit any non-BigInt value diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 9460e3b2bb..079dc13c35 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -139,6 +139,8 @@ pub const Inst = struct { ensure_result_non_error, /// Create a `E!T` type. error_union_type, + /// Create an error set. + error_set, /// Export the provided Decl as the provided name in the compilation's output object file. @"export", /// Given a pointer to a struct or object that contains virtual fields, returns a pointer @@ -359,6 +361,7 @@ pub const Inst = struct { .condbr => CondBr, .ptr_type => PtrType, .enum_literal => EnumLiteral, + .error_set => ErrorSet, }; } @@ -454,6 +457,7 @@ pub const Inst = struct { .anyframe_type, .error_union_type, .bitnot, + .error_set, => false, .@"break", @@ -924,6 +928,16 @@ pub const Inst = struct { }, kw_args: struct {}, }; + + pub const ErrorSet = struct { + pub const base_tag = Tag.error_set; + base: Inst, + + positionals: struct { + fields: [][]const u8, + }, + kw_args: struct {}, + }; }; pub const ErrorMsg = struct { @@ -1158,6 +1172,16 @@ const Writer = struct { const name = self.loop_table.get(param).?; return std.zig.renderStringLiteral(name, stream); }, + [][]const u8 => { + try stream.writeByte('['); + for (param) |str, i| { + if (i != 0) { + try stream.writeAll(", "); + } + try std.zig.renderStringLiteral(str, stream); + } + try stream.writeByte(']'); + }, else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), } } @@ -1555,6 +1579,21 @@ const Parser = struct { const name = try self.parseStringLiteral(); return self.loop_table.get(name).?; }, + [][]const u8 => { + try requireEatBytes(self, "["); + skipSpace(self); + if (eatByte(self, ']')) return &[0][]const u8{}; + + var strings = std.ArrayList([]const u8).init(&self.arena.allocator); + while (true) { + skipSpace(self); + try strings.append(try self.parseStringLiteral()); + skipSpace(self); + if (!eatByte(self, ',')) break; + } + try requireEatBytes(self, "]"); + return strings.toOwnedSlice(); + }, else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), } return self.fail("TODO parse parameter {}", .{@typeName(T)}); diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index bfe58daf7e..6b30d58fdb 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -126,6 +126,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .merge_error_sets => return analyzeInstMergeErrorSets(mod, scope, old_inst.castTag(.merge_error_sets).?), .error_union_type => return analyzeInstErrorUnionType(mod, scope, old_inst.castTag(.error_union_type).?), .anyframe_type => return analyzeInstAnyframeType(mod, scope, old_inst.castTag(.anyframe_type).?), + .error_set => return analyzeInstErrorSet(mod, scope, old_inst.castTag(.error_set).?), } } @@ -435,6 +436,7 @@ fn analyzeInstStr(mod: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerErr // The bytes references memory inside the ZIR module, which can get deallocated // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena. var new_decl_arena = std.heap.ArenaAllocator.init(mod.gpa); + errdefer new_decl_arena.deinit(); const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes); const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0); @@ -737,6 +739,30 @@ fn analyzeInstAnyframeType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) In return mod.constType(scope, inst.base.src, try mod.anyframeType(scope, return_type)); } +fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) InnerError!*Inst { + // The bytes references memory inside the ZIR module, which can get deallocated + // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena. + var new_decl_arena = std.heap.ArenaAllocator.init(mod.gpa); + errdefer new_decl_arena.deinit(); + + const payload = try scope.arena().create(Value.Payload.ErrorSet); + payload.* = .{ .fields = .{} }; + try payload.fields.ensureCapacity(&new_decl_arena.allocator, inst.positionals.fields.len); + + for (inst.positionals.fields) |field_name| { + const value = try mod.getErrorValue(field_name); + if (payload.fields.fetchPutAssumeCapacity(field_name, value)) |prev| { + return mod.fail(scope, inst.base.src, "duplicate error: '{}'", .{field_name}); + } + } + // TODO create name in format "error:line:column" + const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{ + .ty = Type.initTag(.type), + .val = Value.initPayload(&payload.base), + }); + return mod.analyzeDeclRef(scope, inst.base.src, new_decl); +} + fn analyzeInstMergeErrorSets(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { return mod.fail(scope, inst.base.src, "TODO implement merge_error_sets", .{}); } @@ -1377,7 +1403,7 @@ fn analyzeInstPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.PtrType) Inne if (host_size != 0 and bit_offset >= host_size * 8) return mod.fail(scope, inst.base.src, "bit offset starts after end of host integer", .{}); - + const sentinel = if (inst.kw_args.sentinel) |some| (try resolveInstConst(mod, scope, some)).val else