Skip to content

add std.error_set helpers (containsAll,Excluding,Intersect) #19092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions lib/std/error_set.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//! This module contains utility functions for working with error sets.

const std = @import("std");
const assert = std.debug.assert;
const testing = std.testing;
const Type = std.builtin.Type;

/// Returns whether ErrorSet contains all members of ErrorSetToCheckBeingFullyContained.
pub fn containsAll(comptime ErrorSet: type, comptime ErrorSetToCheckBeingFullyContained: type) bool {
comptime assert(@typeInfo(ErrorSet) == .ErrorSet);
comptime assert(@typeInfo(ErrorSetToCheckBeingFullyContained) == .ErrorSet);
return (ErrorSet || ErrorSetToCheckBeingFullyContained) == ErrorSet;
}
test containsAll {
comptime {
assert(containsAll(error{ A, B }, error{ A, B }));
assert(containsAll(anyerror, error{ A, B }));
assert(containsAll(error{C}, error{C}));
assert(!containsAll(error{C}, error{D}));
assert(!containsAll(error{}, error{F}));
assert(containsAll(error{}, error{}));
assert(containsAll(anyerror, error{}));
assert(containsAll(anyerror, anyerror));
}
}
/// Returns whether ErrorSet contains error_to_check_being_contained.
pub fn contains(comptime ErrorSet: type, comptime error_to_check_being_contained: anyerror) bool {
const ErrorToCheckBeingContained = @TypeOf(@field(anyerror, @errorName(error_to_check_being_contained)));
return containsAll(ErrorSet, ErrorToCheckBeingContained);
}
test contains {
comptime {
assert(contains(error{ A, B, C }, error.B));
assert(contains(anyerror, error.B));
assert(!contains(error{ A, B, C }, error.D));
assert(!contains(error{}, error.D));
}
}

/// Returns the set of errors that are
/// members of BaseErrorSet, but not members of ToExcludeErrorSet.
/// ToExcludeErrorSet does not have to
/// be contained in, or even intersect BaseErrorSet.
/// See also `ExcludingAssertAllContained`.
pub fn Excluding(comptime BaseErrorSet: type, comptime ToExcludeErrorSet: type) type {
if (BaseErrorSet == ToExcludeErrorSet) return error{}; //allows excluding anyerror from anyerror

const base_info = @typeInfo(BaseErrorSet);
comptime assert(base_info == .ErrorSet);
const non_anyerror_base_info = base_info.ErrorSet.?; //Type.ErrorSet of null means anyerror, which is currently unsupported as BaseErrorSet
comptime var remaining_error_count = 0;
comptime var remaining_error_buffer: [non_anyerror_base_info.len]Type.Error = undefined;
inline for (non_anyerror_base_info) |error_of_set| {
if (comptime !contains(ToExcludeErrorSet, @field(anyerror, error_of_set.name))) {
remaining_error_buffer[remaining_error_count] = error_of_set;
remaining_error_count += 1;
}
}
return @Type(.{ .ErrorSet = remaining_error_buffer[0..remaining_error_count] });
}
test Excluding {
comptime {
assert(Excluding(error{ A, B, C, D, E, F }, error{ B, C, E }) == error{ A, D, F });
assert(Excluding(error{ A, B }, error{ B, D }) == error{A});
assert(Excluding(error{ B, D }, error{ B, C, D, E }) == error{});
assert(Excluding(error{ A, B, C, D, E, F }, anyerror) == error{});
assert(Excluding(anyerror, anyerror) == error{});
assert(Excluding(error{}, anyerror) == error{});
assert(Excluding(error{ A, B }, error{}) == error{ A, B });
assert(Excluding(error{}, error{}) == error{});
}
}
/// Returns an error set with all members of BaseErrorSet
/// except for error_to_exclude.
/// error_to_exclude does not have to be a member of BaseErrorSet.
/// See also `WithoutAssertContained`.
pub fn Without(comptime BaseErrorSet: type, comptime error_to_exclude: anyerror) type {
const ErrorSetToExclude = @TypeOf(@field(anyerror, @errorName(error_to_exclude)));
return comptime Excluding(BaseErrorSet, ErrorSetToExclude);
}
test Without {
comptime {
assert(Without(error{ A, B, C }, error.B) == error{ A, C });
assert(Without(error{ A, B }, error.A) == error{B});
assert(Without(error{ A, B }, error.C) == error{ A, B });
assert(Without(error{D}, error.D) == error{});
assert(Without(error{}, error.A) == error{});
}
}

/// Returns an error set with all errors which are
/// members of both ErrorSetA and ErrorSetB.
/// The resulting error set may be empty.
/// See also `IntersectAssertNonEmpty`.
pub fn Intersect(comptime ErrorSetA: type, comptime ErrorSetB: type) type {
if (ErrorSetA == anyerror) return ErrorSetB; //allows intersecting with anyerror

const base_info = @typeInfo(ErrorSetA);
comptime assert(base_info == .ErrorSet);
const non_anyerror_base_info = base_info.ErrorSet.?; //anyerror checked above
comptime var remaining_error_count = 0;
comptime var remaining_error_buffer: [non_anyerror_base_info.len]Type.Error = undefined;
inline for (non_anyerror_base_info) |error_of_set| {
if (comptime contains(ErrorSetB, @field(anyerror, error_of_set.name))) {
remaining_error_buffer[remaining_error_count] = error_of_set;
remaining_error_count += 1;
}
}
return @Type(.{ .ErrorSet = remaining_error_buffer[0..remaining_error_count] });
}
test Intersect {
comptime {
assert(Intersect(error{ A, B }, error{ B, D }) == error{B});
assert(Intersect(error{ B, D }, error{ B, C, D, E }) == error{ B, D });
assert(Intersect(error{ A, E, F }, anyerror) == error{ A, E, F });
assert(Intersect(anyerror, anyerror) == anyerror);
assert(Intersect(error{}, anyerror) == error{});
assert(Intersect(error{ A, B }, error{G}) == error{});
assert(Intersect(error{}, error{}) == error{});
}
}

/// Returns the set of errors that are
/// members of BaseErrorSet, but not members of ToExcludeErrorSet.
/// Asserts that all members of ToExcludeErrorSet are members of BaseErrorSet.
/// See also `Excluding`.
pub fn ExcludingAssertAllContained(comptime BaseErrorSet: type, comptime ToExcludeErrorSet: type) type {
comptime assert(containsAll(BaseErrorSet, ToExcludeErrorSet));
return comptime Excluding(BaseErrorSet, ToExcludeErrorSet);
}
/// Returns an error set with all members of BaseErrorSet
/// except for error_to_exclude.
/// Asserts that error_to_exclude is a member of BaseErrorSet.
/// See also `Without`.
pub fn WithoutAssertContained(comptime BaseErrorSet: type, comptime error_to_exclude: anyerror) type {
comptime assert(contains(BaseErrorSet, error_to_exclude));
return comptime Without(BaseErrorSet, error_to_exclude);
}
/// Returns an error set with all errors which are
/// members of both ErrorSetA and ErrorSetB.
/// Asserts that the resulting error set is not empty.
/// See also `Intersect`.
pub fn IntersectAssertNonEmpty(comptime ErrorSetA: type, comptime ErrorSetB: type) type {
const Result = Intersect(ErrorSetA, ErrorSetB);
comptime assert(Result != error{});
return Result;
}
46 changes: 8 additions & 38 deletions lib/std/fs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -514,44 +514,14 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags);
}

// This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded
pub const SelfExePathError = error{
FileNotFound,
AccessDenied,
NameTooLong,
NotSupported,
NotDir,
SymLinkLoop,
InputOutput,
FileTooBig,
IsDir,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
NoDevice,
SystemResources,
NoSpaceLeft,
FileSystem,
BadPathName,
DeviceBusy,
SharingViolation,
PipeBusy,
NotLink,
PathAlreadyExists,

/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,

/// On Windows, antivirus software is enabled by default. It can be
/// disabled, but Windows Update sometimes ignores the user's preference
/// and re-enables it. When enabled, antivirus software on Windows
/// intercepts file system operations and makes them significantly slower
/// in addition to possibly failing with this error code.
AntivirusInterference,

/// On Windows, the volume does not contain a recognized file system. File
/// system drivers might not be loaded, or the volume may be corrupt.
UnrecognizedVolume,
} || posix.SysCtlError;
pub const SelfExePathError = std.error_set.ExcludingAssertAllContained(
posix.ReadLinkError || posix.RealPathError || posix.SysCtlError,
error{
UnsupportedReparsePointType, // Windows-only readlink error; readlink is not called in the Windows implementation
InvalidUtf8, // WASI-only error; selfExePath is not supported on WASI
InvalidWtf8, // selfExePath only converts from WTF-16 -> WTF-8 which cannot fail
},
);

/// `selfExePath` except allocates the result on the heap.
/// Caller owns returned memory.
Expand Down
1 change: 1 addition & 0 deletions lib/std/std.zig
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub const debug = @import("debug.zig");
pub const dwarf = @import("dwarf.zig");
pub const elf = @import("elf.zig");
pub const enums = @import("enums.zig");
pub const error_set = @import("error_set.zig");
pub const fifo = @import("fifo.zig");
pub const fmt = @import("fmt.zig");
pub const fs = @import("fs.zig");
Expand Down