Commit e0f0e2aace

Andrew Kelley <andrew@ziglang.org>
2019-10-09 00:09:08
generated docs: error sets in fn docs
1 parent f74c29b
lib/std/special/docs/index.html
@@ -87,7 +87,7 @@
       #listPkgs li a {
         display: block;
         color: #000;
-        padding: 8px 16px;
+        padding: 0.5em 1em;
         text-decoration: none;
       }
       #listPkgs li a:hover {
@@ -145,6 +145,13 @@
       #listSearchResults li.selected {
         background-color: #93e196;
       }
+
+      #tableFnErrors tr td:first-child{
+        text-align: right;
+        font-weight: bold;
+        vertical-align: top;
+      }
+
       .tok-kw {
           color: #333;
           font-weight: bold;
@@ -262,7 +269,13 @@
     </div>
     <h1 id="hdrName" class="hidden"></h1>
     <div id="fnDocs" class="hidden"></div>
-    <div id="fnErrors" class="hidden"></div>
+    <div id="sectFnErrors" class="hidden">
+      <h2>Errors</h2>
+      <div id="fnErrorsAnyError">
+        <p><span class="tok-type">anyerror</span> means the error set is known only at runtime.</p>
+      </div>
+      <table id="tableFnErrors"><tbody id="listFnErrors"></tbody></table>
+    </div>
     <div id="fnExamples" class="hidden"></div>
     <div id="fnNoExamples" class="hidden">
       <p>This function is not tested or referenced.</p>
lib/std/special/docs/main.js
@@ -13,7 +13,10 @@
     var domFnProto = document.getElementById("fnProto");
     var domFnProtoCode = document.getElementById("fnProtoCode");
     var domFnDocs = document.getElementById("fnDocs");
-    var domFnErrors = document.getElementById("fnErrors");
+    var domSectFnErrors = document.getElementById("sectFnErrors");
+    var domListFnErrors = document.getElementById("listFnErrors");
+    var domTableFnErrors = document.getElementById("tableFnErrors");
+    var domFnErrorsAnyError = document.getElementById("fnErrorsAnyError");
     var domFnExamples = document.getElementById("fnExamples");
     var domFnNoExamples = document.getElementById("fnNoExamples");
     var domSearch = document.getElementById("search");
@@ -36,6 +39,7 @@
     var typeKindFloatId;
     var typeKindIntId;
     var typeKindBoolId;
+    var typeKindVoidId;
     var typeKindErrSetId;
     var typeKindErrUnionId;
     findTypeKinds();
@@ -102,9 +106,11 @@
         domSectInfo.classList.add("hidden");
         domHdrName.classList.add("hidden");
         domSectNav.classList.add("hidden");
-        domFnErrors.classList.add("hidden");
+        domSectFnErrors.classList.add("hidden");
         domFnExamples.classList.add("hidden");
         domFnNoExamples.classList.add("hidden");
+        domFnErrorsAnyError.classList.add("hidden");
+        domTableFnErrors.classList.add("hidden");
 
         renderTitle();
         renderInfo();
@@ -202,6 +208,51 @@
             docsSource = srcNode.docs;
         }
 
+        var errSetTypeIndex = null;
+        if (typeObj.ret != null) {
+            var retType = zigAnalysis.types[typeObj.ret];
+            if (retType.kind === typeKindErrSetId) {
+                errSetTypeIndex = typeObj.ret;
+            } else if (retType.kind === typeKindErrUnionId) {
+                errSetTypeIndex = retType.err;
+            }
+        }
+        if (errSetTypeIndex != null) {
+            var errSetType = zigAnalysis.types[errSetTypeIndex];
+            if (errSetType.errors == null) {
+                domFnErrorsAnyError.classList.remove("hidden");
+            } else {
+                var errorList = [];
+                for (var i = 0; i < errSetType.errors.length; i += 1) {
+                    var errObj = zigAnalysis.errors[errSetType.errors[i]];
+                    var srcObj = zigAnalysis.astNodes[errObj.src];
+                    errorList.push({
+                        err: errObj,
+                        docs: srcObj.docs,
+                    });
+                }
+                errorList.sort(function(a, b) {
+                    return operatorCompare(a.err.name.toLowerCase(), b.err.name.toLowerCase());
+                });
+
+                resizeDomList(domListFnErrors, errorList.length, '<tr><td></td><td></td></tr>');
+                for (var i = 0; i < errorList.length; i += 1) {
+                    var trDom = domListFnErrors.children[i];
+                    var nameTdDom = trDom.children[0];
+                    var descTdDom = trDom.children[1];
+                    nameTdDom.textContent = errorList[i].err.name;
+                    var docs = errorList[i].docs;
+                    if (docs != null) {
+                        descTdDom.innerHTML = markdown(docs);
+                    } else {
+                        descTdDom.textContent = "";
+                    }
+                }
+                domTableFnErrors.classList.remove("hidden");
+            }
+            domSectFnErrors.classList.remove("hidden");
+        }
+
         var protoSrcIndex;
         if (typeIsGenericFn(fnDecl.type)) {
             protoSrcIndex = fnDecl.value;
@@ -285,9 +336,11 @@
         var list = [];
         for (var key in rootPkg.table) {
             if (key === "root" && rootIsStd) continue;
+            var pkgIndex = rootPkg.table[key];
+            if (zigAnalysis.packages[pkgIndex] == null) continue;
             list.push({
                 name: key,
-                pkg: rootPkg.table[key],
+                pkg: pkgIndex,
             });
         }
         list.sort(function(a, b) {
@@ -447,6 +500,12 @@
                 } else {
                     return "bool";
                 }
+            case typeKindVoidId:
+                if (wantHtml) {
+                    return '<span class="tok-type">void</span>';
+                } else {
+                    return "void";
+                }
             case typeKindErrSetId:
                 if (typeObj.errors == null) {
                     if (wantHtml) {
@@ -580,6 +639,7 @@
             return false;
         }
         var stdPkg = zigAnalysis.packages[rootPkg.table["std"]];
+        if (stdPkg == null) return false;
         return rootPkg.file === stdPkg.file;
     }
 
@@ -597,6 +657,8 @@
                 typeKindIntId = i;
             } else if (zigAnalysis.typeKinds[i] === "Bool") {
                 typeKindBoolId = i;
+            } else if (zigAnalysis.typeKinds[i] === "Void") {
+                typeKindVoidId = i;
             } else if (zigAnalysis.typeKinds[i] === "ErrorSet") {
                 typeKindErrSetId = i;
             } else if (zigAnalysis.typeKinds[i] === "ErrorUnion") {
@@ -621,6 +683,9 @@
         if (typeKindBoolId == null) {
             throw new Error("No type kind 'Bool' found");
         }
+        if (typeKindVoidId == null) {
+            throw new Error("No type kind 'Void' found");
+        }
         if (typeKindErrSetId == null) {
             throw new Error("No type kind 'ErrorSet' found");
         }
@@ -702,10 +767,11 @@
             for (var key in item.pkg.table) {
                 var childPkgIndex = item.pkg.table[key];
                 if (list[childPkgIndex] != null) continue;
+                var childPkg = zigAnalysis.packages[childPkgIndex];
+                if (childPkg == null) continue;
 
                 var newPath = item.path.concat([key])
                 list[childPkgIndex] = newPath;
-                var childPkg = zigAnalysis.packages[childPkgIndex];
                 stack.push({
                     path: newPath,
                     pkg: childPkg,
lib/std/os.zig
@@ -183,7 +183,7 @@ pub fn abort() noreturn {
     exit(127);
 }
 
-pub const RaiseError = error{Unexpected};
+pub const RaiseError = UnexpectedError;
 
 pub fn raise(sig: u8) RaiseError!void {
     if (builtin.link_libc) {
@@ -215,10 +215,7 @@ pub fn raise(sig: u8) RaiseError!void {
     @compileError("std.os.raise unimplemented for this target");
 }
 
-pub const KillError = error{
-    PermissionDenied,
-    Unexpected,
-};
+pub const KillError = error{PermissionDenied} || UnexpectedError;
 
 pub fn kill(pid: pid_t, sig: u8) KillError!void {
     switch (errno(system.kill(pid, sig))) {
@@ -266,9 +263,7 @@ pub const ReadError = error{
     /// This error occurs when no global event loop is configured,
     /// and reading from the file descriptor would block.
     WouldBlock,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Returns the number of bytes that were read, which can be less than
 /// buf.len. If 0 bytes were read, that means EOF.
@@ -385,8 +380,7 @@ pub const WriteError = error{
     BrokenPipe,
     SystemResources,
     OperationAborted,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Write to a file descriptor. Keeps trying if it gets interrupted.
 /// This function is for blocking file descriptors only.
@@ -548,8 +542,7 @@ pub const OpenError = error{
     NotDir,
     PathAlreadyExists,
     DeviceBusy,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Open and possibly create a file. Keeps trying if it gets interrupted.
 /// See also `openC`.
@@ -748,9 +741,7 @@ pub const ExecveError = error{
     ProcessFdQuotaExceeded,
     SystemFdQuotaExceeded,
     NameTooLong,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 fn execveErrnoToErr(err: usize) ExecveError {
     assert(err > 0);
@@ -808,8 +799,7 @@ pub fn getenvC(key: [*]const u8) ?[]const u8 {
 pub const GetCwdError = error{
     NameTooLong,
     CurrentWorkingDirectoryUnlinked,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// The result is a slice of out_buffer, indexed from 0.
 pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
@@ -846,8 +836,7 @@ pub const SymLinkError = error{
     NameTooLong,
     InvalidUtf8,
     BadPathName,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
 /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
@@ -932,7 +921,6 @@ pub const UnlinkError = error{
     NotDir,
     SystemResources,
     ReadOnlyFileSystem,
-    Unexpected,
 
     /// On Windows, file paths must be valid Unicode.
     InvalidUtf8,
@@ -940,7 +928,7 @@ pub const UnlinkError = error{
     /// On Windows, file paths cannot contain these characters:
     /// '/', '*', '?', '"', '<', '>', '|'
     BadPathName,
-};
+} || UnexpectedError;
 
 /// Delete a name and possibly the file it refers to.
 /// See also `unlinkC`.
@@ -996,8 +984,7 @@ const RenameError = error{
     RenameAcrossMountPoints,
     InvalidUtf8,
     BadPathName,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Change the name or location of a file.
 pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
@@ -1064,8 +1051,7 @@ pub const MakeDirError = error{
     ReadOnlyFileSystem,
     InvalidUtf8,
     BadPathName,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Create a directory.
 /// `mode` is ignored on Windows.
@@ -1116,8 +1102,7 @@ pub const DeleteDirError = error{
     ReadOnlyFileSystem,
     InvalidUtf8,
     BadPathName,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Deletes an empty directory.
 pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
@@ -1163,8 +1148,7 @@ pub const ChangeCurDirError = error{
     FileNotFound,
     SystemResources,
     NotDir,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Changes the current working directory of the calling process.
 /// `dir_path` is recommended to be a UTF-8 encoded string.
@@ -1206,8 +1190,7 @@ pub const ReadLinkError = error{
     FileNotFound,
     SystemResources,
     NotDir,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Read value of a symbolic link.
 /// The return value is a slice of `out_buffer` from index 0.
@@ -1247,8 +1230,7 @@ pub const SetIdError = error{
     ResourceLimitReached,
     InvalidUserId,
     PermissionDenied,
-    Unexpected,
-};
+} || UnexpectedError;
 
 pub fn setuid(uid: u32) SetIdError!void {
     switch (errno(system.setuid(uid))) {
@@ -1357,9 +1339,7 @@ pub const SocketError = error{
 
     /// The protocol type or the specified protocol is not supported within this domain.
     ProtocolNotSupported,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!i32 {
     const rc = system.socket(domain, socket_type, protocol);
@@ -1409,9 +1389,7 @@ pub const BindError = error{
 
     /// The socket inode would reside on a read-only filesystem.
     ReadOnlyFileSystem,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// addr is `*const T` where T is one of the sockaddr
 pub fn bind(fd: i32, addr: *const sockaddr) BindError!void {
@@ -1448,9 +1426,7 @@ const ListenError = error{
 
     /// The socket is not of a type that supports the listen() operation.
     OperationNotSupported,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 pub fn listen(sockfd: i32, backlog: u32) ListenError!void {
     const rc = system.listen(sockfd, backlog);
@@ -1487,9 +1463,7 @@ pub const AcceptError = error{
 
     /// Firewall rules forbid connection.
     BlockedByFirewall,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Accept a connection on a socket. `fd` must be opened in blocking mode.
 /// See also `accept4_async`.
@@ -1559,9 +1533,7 @@ pub const EpollCreateError = error{
 
     /// There was insufficient memory to create the kernel object.
     SystemResources,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 pub fn epoll_create1(flags: u32) EpollCreateError!i32 {
     const rc = system.epoll_create1(flags);
@@ -1600,9 +1572,7 @@ pub const EpollCtlError = error{
     /// The target file fd does not support epoll.  This error can occur if fd refers to,
     /// for example, a regular file or a directory.
     FileDescriptorIncompatibleWithEpoll,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 pub fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: ?*epoll_event) EpollCtlError!void {
     const rc = system.epoll_ctl(epfd, op, fd, event);
@@ -1643,8 +1613,7 @@ pub const EventFdError = error{
     SystemResources,
     ProcessFdQuotaExceeded,
     SystemFdQuotaExceeded,
-    Unexpected,
-};
+} || UnexpectedError;
 
 pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 {
     const rc = system.eventfd(initval, flags);
@@ -1663,9 +1632,7 @@ pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 {
 pub const GetSockNameError = error{
     /// Insufficient resources were available in the system to perform the operation.
     SystemResources,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 pub fn getsockname(sockfd: i32) GetSockNameError!sockaddr {
     var addr: sockaddr = undefined;
@@ -1714,9 +1681,7 @@ pub const ConnectError = error{
     /// Timeout  while  attempting  connection.   The server may be too busy to accept new connections.  Note
     /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
     ConnectionTimedOut,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Initiate a connection on a socket.
 /// This is for blocking file descriptors only.
@@ -1824,10 +1789,7 @@ pub fn waitpid(pid: i32, flags: u32) u32 {
     }
 }
 
-pub const FStatError = error{
-    SystemResources,
-    Unexpected,
-};
+pub const FStatError = error{SystemResources} || UnexpectedError;
 
 pub fn fstat(fd: fd_t) FStatError!Stat {
     var stat: Stat = undefined;
@@ -1856,9 +1818,7 @@ pub const KQueueError = error{
 
     /// The system-wide limit on the total number of open files has been reached.
     SystemFdQuotaExceeded,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 pub fn kqueue() KQueueError!i32 {
     const rc = system.kqueue();
@@ -1922,8 +1882,7 @@ pub const INotifyInitError = error{
     ProcessFdQuotaExceeded,
     SystemFdQuotaExceeded,
     SystemResources,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// initialize an inotify instance
 pub fn inotify_init1(flags: u32) INotifyInitError!i32 {
@@ -1944,8 +1903,7 @@ pub const INotifyAddWatchError = error{
     FileNotFound,
     SystemResources,
     UserResourceLimitReached,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// add a watch to an initialized inotify instance
 pub fn inotify_add_watch(inotify_fd: i32, pathname: []const u8, mask: u32) INotifyAddWatchError!i32 {
@@ -1992,8 +1950,7 @@ pub const MProtectError = error{
     /// dle of a region currently protected as PROT_READ|PROT_WRITE would result in three map‐
     /// pings: two read/write mappings at each end and a read-only mapping in the middle.)
     OutOfMemory,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// `memory.len` must be page-aligned.
 pub fn mprotect(memory: []align(mem.page_size) u8, protection: u32) MProtectError!void {
@@ -2007,10 +1964,7 @@ pub fn mprotect(memory: []align(mem.page_size) u8, protection: u32) MProtectErro
     }
 }
 
-pub const ForkError = error{
-    SystemResources,
-    Unexpected,
-};
+pub const ForkError = error{SystemResources} || UnexpectedError;
 
 pub fn fork() ForkError!pid_t {
     const rc = system.fork();
@@ -2037,8 +1991,7 @@ pub const MMapError = error{
     PermissionDenied,
     LockedMemoryLimitExceeded,
     OutOfMemory,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Map files or devices into memory.
 /// Use of a mapped region can result in these signals:
@@ -2101,9 +2054,7 @@ pub const AccessError = error{
 
     /// On Windows, file paths must be valid Unicode.
     InvalidUtf8,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// check user's permissions for a file
 /// TODO currently this assumes `mode` is `F_OK` on Windows.
@@ -2161,8 +2112,7 @@ pub fn accessW(path: [*]const u16, mode: u32) windows.GetFileAttributesError!voi
 pub const PipeError = error{
     SystemFdQuotaExceeded,
     ProcessFdQuotaExceeded,
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Creates a unidirectional data channel that can be used for interprocess communication.
 pub fn pipe() PipeError![2]fd_t {
@@ -2193,8 +2143,7 @@ pub const SysCtlError = error{
     PermissionDenied,
     SystemResources,
     NameTooLong,
-    Unexpected,
-};
+} || UnexpectedError;
 
 pub fn sysctl(
     name: []const c_int,
@@ -2237,10 +2186,7 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
     }
 }
 
-pub const SeekError = error{
-    Unseekable,
-    Unexpected,
-};
+pub const SeekError = error{Unseekable} || UnexpectedError;
 
 /// Repositions read/write file offset relative to the beginning.
 pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
@@ -2382,9 +2328,7 @@ pub const RealPathError = error{
     InvalidUtf8,
 
     PathAlreadyExists,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 /// Return the canonicalized absolute pathname.
 /// Expands all symbolic links and resolves references to `.`, `..`, and
@@ -2548,10 +2492,7 @@ pub fn dl_iterate_phdr(comptime T: type, callback: extern fn (info: *dl_phdr_inf
     return last_r;
 }
 
-pub const ClockGetTimeError = error{
-    UnsupportedClock,
-    Unexpected,
-};
+pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError;
 
 pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void {
     switch (errno(system.clock_gettime(clk_id, tp))) {
@@ -2571,10 +2512,7 @@ pub fn clock_getres(clk_id: i32, res: *timespec) ClockGetTimeError!void {
     }
 }
 
-pub const SchedGetAffinityError = error{
-    PermissionDenied,
-    Unexpected,
-};
+pub const SchedGetAffinityError = error{PermissionDenied} || UnexpectedError;
 
 pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t {
     var set: cpu_set_t = undefined;
@@ -2628,8 +2566,7 @@ pub const SigaltstackError = error{
 
     /// Attempted to change the signal stack while it was active.
     PermissionDenied,
-    Unexpected,
-};
+} || UnexpectedError;
 
 pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void {
     if (windows.is_the_target or uefi.is_the_target or wasi.is_the_target)
@@ -2677,9 +2614,7 @@ pub const FutimensError = error{
     PermissionDenied,
 
     ReadOnlyFileSystem,
-
-    Unexpected,
-};
+} || UnexpectedError;
 
 pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void {
     switch (errno(system.futimens(fd, times))) {
@@ -2694,10 +2629,7 @@ pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void {
     }
 }
 
-pub const GetHostNameError = error{
-    PermissionDenied,
-    Unexpected,
-};
+pub const GetHostNameError = error{PermissionDenied} || UnexpectedError;
 
 pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 {
     if (builtin.link_libc) {
src/all_types.hpp
@@ -2146,6 +2146,7 @@ struct ErrorTableEntry {
     Buf name;
     uint32_t value;
     AstNode *decl_node;
+    ErrorTableEntry *other; // null, or another error decl that was merged into this
     ZigType *set_with_only_this_in_it;
     // If we generate a constant error name value for this error, we memoize it here.
     // The type of this is array
src/dump_analysis.cpp
@@ -892,6 +892,9 @@ static void anal_dump_type(AnalDumpCtx *ctx, ZigType *ty) {
             if (type_is_global_error_set(ty)) {
                 break;
             }
+            jw_object_field(jw, "name");
+            jw_string(jw, buf_ptr(&ty->name));
+
             if (ty->data.error_set.infer_fn != nullptr) {
                 jw_object_field(jw, "fn");
                 anal_dump_fn_ref(ctx, ty->data.error_set.infer_fn);
src/ir.cpp
@@ -7892,11 +7892,14 @@ static ZigType *get_error_set_union(CodeGen *g, ErrorTableEntry **errors, ZigTyp
     }
 
     uint32_t index = set1->data.error_set.err_count;
+    bool need_comma = false;
     for (uint32_t i = 0; i < set2->data.error_set.err_count; i += 1) {
         ErrorTableEntry *error_entry = set2->data.error_set.errors[i];
         if (errors[error_entry->value] == nullptr) {
             errors[error_entry->value] = error_entry;
-            buf_appendf(&err_set_type->name, "%s,", buf_ptr(&error_entry->name));
+            const char *comma = need_comma ? "," : "";
+            need_comma = true;
+            buf_appendf(&err_set_type->name, "%s%s", comma, buf_ptr(&error_entry->name));
             err_set_type->data.error_set.errors[index] = error_entry;
             index += 1;
         }
@@ -7927,6 +7930,17 @@ static ZigType *make_err_set_with_one_item(CodeGen *g, Scope *parent_scope, AstN
     return err_set_type;
 }
 
+static AstNode *ast_field_to_symbol_node(AstNode *err_set_field_node) {
+    if (err_set_field_node->type == NodeTypeSymbol) {
+        return err_set_field_node;
+    } else if (err_set_field_node->type == NodeTypeErrorSetField) {
+        assert(err_set_field_node->data.err_set_field.field_name->type == NodeTypeSymbol);
+        return err_set_field_node->data.err_set_field.field_name;
+    } else {
+        return err_set_field_node;
+    }
+}
+
 static IrInstruction *ir_gen_err_set_decl(IrBuilder *irb, Scope *parent_scope, AstNode *node) {
     assert(node->type == NodeTypeErrorSetDecl);
 
@@ -7946,18 +7960,10 @@ static IrInstruction *ir_gen_err_set_decl(IrBuilder *irb, Scope *parent_scope, A
 
     for (uint32_t i = 0; i < err_count; i += 1) {
         AstNode *field_node = node->data.err_set_decl.decls.at(i);
-        AstNode *symbol_node;
-        if (field_node->type == NodeTypeSymbol) {
-            symbol_node = field_node;
-        } else if (field_node->type == NodeTypeErrorSetField) {
-            symbol_node = field_node->data.err_set_field.field_name;
-        } else {
-            zig_unreachable();
-        }
-        assert(symbol_node->type == NodeTypeSymbol);
+        AstNode *symbol_node = ast_field_to_symbol_node(field_node);
         Buf *err_name = symbol_node->data.symbol_expr.symbol;
         ErrorTableEntry *err = allocate<ErrorTableEntry>(1);
-        err->decl_node = symbol_node;
+        err->decl_node = field_node;
         buf_init_from_buf(&err->name, err_name);
 
         auto existing_entry = irb->codegen->error_table.put_unique(err_name, err);
@@ -7973,8 +7979,10 @@ static IrInstruction *ir_gen_err_set_decl(IrBuilder *irb, Scope *parent_scope, A
 
         ErrorTableEntry *prev_err = errors[err->value];
         if (prev_err != nullptr) {
-            ErrorMsg *msg = add_node_error(irb->codegen, err->decl_node, buf_sprintf("duplicate error: '%s'", buf_ptr(&err->name)));
-            add_error_note(irb->codegen, msg, prev_err->decl_node, buf_sprintf("other error here"));
+            ErrorMsg *msg = add_node_error(irb->codegen, ast_field_to_symbol_node(err->decl_node),
+                    buf_sprintf("duplicate error: '%s'", buf_ptr(&err->name)));
+            add_error_note(irb->codegen, msg, ast_field_to_symbol_node(prev_err->decl_node),
+                    buf_sprintf("other error here"));
             return irb->codegen->invalid_instruction;
         }
         errors[err->value] = err;
@@ -9479,6 +9487,14 @@ static void populate_error_set_table(ErrorTableEntry **errors, ZigType *set) {
     }
 }
 
+static ErrorTableEntry *better_documented_error(ErrorTableEntry *preferred, ErrorTableEntry *other) {
+    if (preferred->decl_node->type == NodeTypeErrorSetField)
+        return preferred;
+    if (other->decl_node->type == NodeTypeErrorSetField)
+        return other;
+    return preferred;
+}
+
 static ZigType *get_error_set_intersection(IrAnalyze *ira, ZigType *set1, ZigType *set2,
         AstNode *source_node)
 {
@@ -9505,12 +9521,17 @@ static ZigType *get_error_set_intersection(IrAnalyze *ira, ZigType *set1, ZigTyp
     buf_resize(&err_set_type->name, 0);
     buf_appendf(&err_set_type->name, "error{");
 
+    bool need_comma = false;
     for (uint32_t i = 0; i < set2->data.error_set.err_count; i += 1) {
         ErrorTableEntry *error_entry = set2->data.error_set.errors[i];
         ErrorTableEntry *existing_entry = errors[error_entry->value];
         if (existing_entry != nullptr) {
-            intersection_list.append(existing_entry);
-            buf_appendf(&err_set_type->name, "%s,", buf_ptr(&existing_entry->name));
+            // prefer the one with docs
+            const char *comma = need_comma ? "," : "";
+            need_comma = true;
+            ErrorTableEntry *existing_entry_with_docs = better_documented_error(existing_entry, error_entry);
+            intersection_list.append(existing_entry_with_docs);
+            buf_appendf(&err_set_type->name, "%s%s", comma, buf_ptr(&existing_entry_with_docs->name));
         }
     }
     free(errors);
@@ -12058,7 +12079,7 @@ static void report_recursive_error(IrAnalyze *ira, AstNode *source_node, ConstCa
             ZigList<ErrorTableEntry *> *missing_errors = &cast_result->data.error_set_mismatch->missing_errors;
             for (size_t i = 0; i < missing_errors->length; i += 1) {
                 ErrorTableEntry *error_entry = missing_errors->at(i);
-                add_error_note(ira->codegen, parent_msg, error_entry->decl_node,
+                add_error_note(ira->codegen, parent_msg, ast_field_to_symbol_node(error_entry->decl_node),
                     buf_sprintf("'error.%s' not a member of destination error set", buf_ptr(&error_entry->name)));
             }
             break;
src/parser.cpp
@@ -498,8 +498,12 @@ static AstNode *ast_parse_root(ParseContext *pc) {
 }
 
 static Token *ast_parse_doc_comments(ParseContext *pc, Buf *buf) {
+    Token *first_doc_token = nullptr;
     Token *doc_token = nullptr;
     while ((doc_token = eat_token_if(pc, TokenIdDocComment))) {
+        if (first_doc_token == nullptr) {
+            first_doc_token = doc_token;
+        }
         if (buf->list.length == 0) {
             buf_resize(buf, 0);
         }
@@ -507,7 +511,7 @@ static Token *ast_parse_doc_comments(ParseContext *pc, Buf *buf) {
         buf_append_mem(buf, buf_ptr(pc->buf) + doc_token->start_pos + 3,
                 doc_token->end_pos - doc_token->start_pos - 3);
     }
-    return doc_token;
+    return first_doc_token;
 }
 
 // ContainerMembers