diff --git a/CMakeLists.txt b/CMakeLists.txt index 8aa6dff6a3..c23c0b6e17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -480,11 +480,12 @@ set(ZIG_STD_FILES "os/child_process.zig" "os/darwin.zig" "os/darwin_errno.zig" + "os/file.zig" "os/get_user_id.zig" "os/index.zig" - "os/linux/index.zig" "os/linux/errno.zig" "os/linux/i386.zig" + "os/linux/index.zig" "os/linux/x86_64.zig" "os/path.zig" "os/windows/error.zig" @@ -520,6 +521,10 @@ set(ZIG_STD_FILES "special/panic.zig" "special/test_runner.zig" "unicode.zig" + "zig/ast.zig" + "zig/index.zig" + "zig/parser.zig" + "zig/tokenizer.zig" ) set(ZIG_C_HEADER_FILES diff --git a/build.zig b/build.zig index 0a898776f8..0a7795d615 100644 --- a/build.zig +++ b/build.zig @@ -10,7 +10,7 @@ const ArrayList = std.ArrayList; const Buffer = std.Buffer; const io = std.io; -pub fn build(b: &Builder) %void { +pub fn build(b: &Builder) !void { const mode = b.standardReleaseOptions(); var docgen_exe = b.addExecutable("docgen", "doc/docgen.zig"); @@ -78,7 +78,6 @@ pub fn build(b: &Builder) %void { exe.linkSystemLibrary("c"); b.default_step.dependOn(&exe.step); - b.default_step.dependOn(docs_step); const skip_self_hosted = b.option(bool, "skip-self-hosted", "Main test suite skips building self hosted compiler") ?? false; if (!skip_self_hosted) { @@ -108,10 +107,6 @@ pub fn build(b: &Builder) %void { "std/special/compiler_rt/index.zig", "compiler-rt", "Run the compiler_rt tests", with_lldb)); - test_step.dependOn(tests.addPkgTests(b, test_filter, - "src-self-hosted/main.zig", "fmt", "Run the fmt tests", - with_lldb)); - test_step.dependOn(tests.addCompareOutputTests(b, test_filter)); test_step.dependOn(tests.addBuildExampleTests(b, test_filter)); test_step.dependOn(tests.addCompileErrorTests(b, test_filter)); @@ -149,7 +144,7 @@ const LibraryDep = struct { includes: ArrayList([]const u8), }; -fn findLLVM(b: &Builder, llvm_config_exe: []const u8) %LibraryDep { +fn findLLVM(b: &Builder, llvm_config_exe: []const u8) !LibraryDep { const libs_output = try b.exec([][]const u8{llvm_config_exe, "--libs", "--system-libs"}); const includes_output = try b.exec([][]const u8{llvm_config_exe, "--includedir"}); const libdir_output = try b.exec([][]const u8{llvm_config_exe, "--libdir"}); diff --git a/ci/appveyor/build_script.bat b/ci/appveyor/build_script.bat index 235b0e515e..d0d3f61b34 100644 --- a/ci/appveyor/build_script.bat +++ b/ci/appveyor/build_script.bat @@ -24,18 +24,6 @@ cd %ZIGBUILDDIR% cmake.exe .. -Thost=x64 -G"Visual Studio 14 2015 Win64" "-DCMAKE_INSTALL_PREFIX=%ZIGBUILDDIR%" "-DCMAKE_PREFIX_PATH=%ZIGPREFIXPATH%" -DCMAKE_BUILD_TYPE=Release "-DZIG_LIBC_INCLUDE_DIR=C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt" "-DZIG_LIBC_LIB_DIR=C:\Program Files (x86)\Windows Kits\10\bin\x64\ucrt" "-DZIG_LIBC_STATIC_LIB_DIR=C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64" || exit /b msbuild /p:Configuration=Release INSTALL.vcxproj || exit /b -bin\zig.exe build --build-file ..\build.zig test -Dverbose-link || exit /b +bin\zig.exe build --build-file ..\build.zig test || exit /b -@echo "MSVC build succeeded, proceeding with MinGW build" -cd %APPVEYOR_BUILD_FOLDER% -SET "PATH=C:\msys64\mingw64\bin;C:\msys64\usr\bin;%PATH%" -SET "MSYSTEM=MINGW64" - -bash -lc "pacman -Syu --needed --noconfirm" -bash -lc "pacman -Su --needed --noconfirm" - -bash -lc "pacman -S --needed --noconfirm make mingw64/mingw-w64-x86_64-make mingw64/mingw-w64-x86_64-cmake mingw64/mingw-w64-x86_64-clang mingw64/mingw-w64-x86_64-llvm mingw64/mingw-w64-x86_64-lld mingw64/mingw-w64-x86_64-gcc" - -bash -lc "cd ${APPVEYOR_BUILD_FOLDER} && mkdir build && cd build && cmake .. -G""MSYS Makefiles"" -DCMAKE_INSTALL_PREFIX=$(pwd) -DZIG_LIBC_LIB_DIR=$(dirname $(cc -print-file-name=crt1.o)) -DZIG_LIBC_INCLUDE_DIR=$(echo -n | cc -E -x c - -v 2>&1 | grep -B1 ""End of search list."" | head -n1 | cut -c 2- | sed ""s/ .*//"") -DZIG_LIBC_STATIC_LIB_DIR=$(dirname $(cc -print-file-name=crtbegin.o)) && make && make install" - -@echo "MinGW build successful" +@echo "MSVC build succeeded" diff --git a/doc/docgen.zig b/doc/docgen.zig index a7b4084e7e..f9aac826ad 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -12,7 +12,7 @@ const exe_ext = std.build.Target(std.build.Target.Native).exeFileExt(); const obj_ext = std.build.Target(std.build.Target.Native).oFileExt(); const tmp_dir_name = "docgen_tmp"; -pub fn main() %void { +pub fn main() !void { // TODO use a more general purpose allocator here var inc_allocator = try std.heap.IncrementingAllocator.init(max_doc_file_size); defer inc_allocator.deinit(); @@ -31,10 +31,10 @@ pub fn main() %void { const out_file_name = try (args_it.next(allocator) ?? @panic("expected output arg")); defer allocator.free(out_file_name); - var in_file = try io.File.openRead(in_file_name, allocator); + var in_file = try os.File.openRead(allocator, in_file_name); defer in_file.close(); - var out_file = try io.File.openWrite(out_file_name, allocator); + var out_file = try os.File.openWrite(allocator, out_file_name); defer out_file.close(); var file_in_stream = io.FileInStream.init(&in_file); @@ -42,7 +42,7 @@ pub fn main() %void { const input_file_bytes = try file_in_stream.stream.readAllAlloc(allocator, max_doc_file_size); var file_out_stream = io.FileOutStream.init(&out_file); - var buffered_out_stream = io.BufferedOutStream.init(&file_out_stream.stream); + var buffered_out_stream = io.BufferedOutStream(io.FileOutStream.Error).init(&file_out_stream.stream); var tokenizer = Tokenizer.init(in_file_name, input_file_bytes); var toc = try genToc(allocator, &tokenizer); @@ -218,8 +218,6 @@ const Tokenizer = struct { } }; -error ParseError; - fn parseError(tokenizer: &Tokenizer, token: &const Token, comptime fmt: []const u8, args: ...) error { const loc = tokenizer.getTokenLocation(token); warn("{}:{}:{}: error: " ++ fmt ++ "\n", tokenizer.source_file_name, loc.line + 1, loc.column + 1, args); @@ -243,13 +241,13 @@ fn parseError(tokenizer: &Tokenizer, token: &const Token, comptime fmt: []const return error.ParseError; } -fn assertToken(tokenizer: &Tokenizer, token: &const Token, id: Token.Id) %void { +fn assertToken(tokenizer: &Tokenizer, token: &const Token, id: Token.Id) !void { if (token.id != id) { return parseError(tokenizer, token, "expected {}, found {}", @tagName(id), @tagName(token.id)); } } -fn eatToken(tokenizer: &Tokenizer, id: Token.Id) %Token { +fn eatToken(tokenizer: &Tokenizer, id: Token.Id) !Token { const token = tokenizer.next(); try assertToken(tokenizer, token, id); return token; @@ -316,7 +314,7 @@ const Action = enum { Close, }; -fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) %Toc { +fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) !Toc { var urls = std.HashMap([]const u8, Token, mem.hash_slice_u8, mem.eql_slice_u8).init(allocator); errdefer urls.deinit(); @@ -540,7 +538,7 @@ fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) %Toc { }; } -fn urlize(allocator: &mem.Allocator, input: []const u8) %[]u8 { +fn urlize(allocator: &mem.Allocator, input: []const u8) ![]u8 { var buf = try std.Buffer.initSize(allocator, 0); defer buf.deinit(); @@ -560,7 +558,7 @@ fn urlize(allocator: &mem.Allocator, input: []const u8) %[]u8 { return buf.toOwnedSlice(); } -fn escapeHtml(allocator: &mem.Allocator, input: []const u8) %[]u8 { +fn escapeHtml(allocator: &mem.Allocator, input: []const u8) ![]u8 { var buf = try std.Buffer.initSize(allocator, 0); defer buf.deinit(); @@ -596,15 +594,13 @@ const TermState = enum { ExpectEnd, }; -error UnsupportedEscape; - test "term color" { const input_bytes = "A\x1b[32;1mgreen\x1b[0mB"; const result = try termColor(std.debug.global_allocator, input_bytes); assert(mem.eql(u8, result, "AgreenB")); } -fn termColor(allocator: &mem.Allocator, input: []const u8) %[]u8 { +fn termColor(allocator: &mem.Allocator, input: []const u8) ![]u8 { var buf = try std.Buffer.initSize(allocator, 0); defer buf.deinit(); @@ -684,9 +680,7 @@ fn termColor(allocator: &mem.Allocator, input: []const u8) %[]u8 { return buf.toOwnedSlice(); } -error ExampleFailedToCompile; - -fn genHtml(allocator: &mem.Allocator, tokenizer: &Tokenizer, toc: &Toc, out: &io.OutStream, zig_exe: []const u8) %void { +fn genHtml(allocator: &mem.Allocator, tokenizer: &Tokenizer, toc: &Toc, out: var, zig_exe: []const u8) !void { var code_progress_index: usize = 0; for (toc.nodes) |node| { switch (node) { @@ -729,7 +723,7 @@ fn genHtml(allocator: &mem.Allocator, tokenizer: &Tokenizer, toc: &Toc, out: &io try out.print("
{}
", escaped_source); const name_plus_ext = try std.fmt.allocPrint(allocator, "{}.zig", code.name); const tmp_source_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_ext); - try io.writeFile(tmp_source_file_name, trimmed_raw_source, null); + try io.writeFile(allocator, tmp_source_file_name, trimmed_raw_source); switch (code.id) { Code.Id.Exe => |expected_outcome| { @@ -974,10 +968,7 @@ fn genHtml(allocator: &mem.Allocator, tokenizer: &Tokenizer, toc: &Toc, out: &io } -error ChildCrashed; -error ChildExitError; - -fn exec(allocator: &mem.Allocator, args: []const []const u8) %os.ChildProcess.ExecResult { +fn exec(allocator: &mem.Allocator, args: []const []const u8) !os.ChildProcess.ExecResult { const result = try os.ChildProcess.exec(allocator, args, null, null, max_doc_file_size); switch (result.term) { os.ChildProcess.Term.Exited => |exit_code| { diff --git a/doc/langref.html.in b/doc/langref.html.in index 29a40c86a5..2b09ca81bd 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -108,7 +108,7 @@ {#code_begin|exe|hello#} const std = @import("std"); -pub fn main() %void { +pub fn main() !void { // If this program is run without stdout attached, exit with an error. var stdout_file = try std.io.getStdOut(); // If this program encounters pipe failure when printing to stdout, exit @@ -129,8 +129,8 @@ pub fn main() void { } {#code_end#}

- Note that we also left off the % from the return type. - In Zig, if your main function cannot fail, you may use the void return type. + Note that we also left off the ! from the return type. + In Zig, if your main function cannot fail, you must use the void return type.

{#see_also|Values|@import|Errors|Root Source File#} {#header_close#} @@ -141,10 +141,7 @@ const warn = std.debug.warn; const os = std.os; const assert = std.debug.assert; -// error declaration, makes `error.ArgNotFound` available -error ArgNotFound; - -pub fn main() %void { +pub fn main() void { // integers const one_plus_one: i32 = 1 + 1; warn("1 + 1 = {}\n", one_plus_one); @@ -173,7 +170,7 @@ pub fn main() %void { @typeName(@typeOf(nullable_value)), nullable_value); // error union - var number_or_error: %i32 = error.ArgNotFound; + var number_or_error: error!i32 = error.ArgNotFound; warn("\nerror union 1\ntype: {}\nvalue: {}\n", @typeName(@typeOf(number_or_error)), number_or_error); @@ -681,7 +678,7 @@ const warn = @import("std").debug.warn; extern fn foo_strict(x: f64) f64; extern fn foo_optimized(x: f64) f64; -pub fn main() %void { +pub fn main() void { const x = 0.001; warn("optimized = {}\n", foo_optimized(x)); warn("strict = {}\n", foo_strict(x)); @@ -1036,7 +1033,7 @@ a catch |err| b err is the error and is in scope of the expression b. -
const value: %u32 = null;
+            
const value: error!u32 = error.Broken;
 const unwrapped = value catch 1234;
 unwrapped == 1234
@@ -1269,9 +1266,10 @@ const ptr = &x; {#header_close#} {#header_open|Precedence#}
x() x[] x.y
-!x -x -%x ~x *x &x ?x %x ??x
+a!b
+!x -x -%x ~x *x &x ?x ??x
 x{}
-* / % ** *%
+! * / % ** *%
 + - ++ +% -%
 << >>
 &
@@ -2268,8 +2266,8 @@ fn eventuallyNullSequence() ?u32 {
         break :blk numbers_left;
     };
 }
-error ReachedZero;
-fn eventuallyErrorSequence() %u32 {
+
+fn eventuallyErrorSequence() error!u32 {
     return if (numbers_left == 0) error.ReachedZero else blk: {
         numbers_left -= 1;
         break :blk numbers_left;
@@ -2398,7 +2396,7 @@ fn typeNameLength(comptime T: type) usize {
 // If expressions have three uses, corresponding to the three types:
 // * bool
 // * ?T
-// * %T
+// * error!T
 
 const assert = @import("std").debug.assert;
 
@@ -2459,20 +2457,18 @@ test "if nullable" {
     }
 }
 
-error BadValue;
-error LessBadValue;
 test "if error union" {
     // If expressions test for errors.
     // Note the |err| capture on the else.
 
-    const a: %u32 = 0;
+    const a: error!u32 = 0;
     if (a) |value| {
         assert(value == 0);
     } else |err| {
         unreachable;
     }
 
-    const b: %u32 = error.BadValue;
+    const b: error!u32 = error.BadValue;
     if (b) |value| {
         unreachable;
     } else |err| {
@@ -2490,7 +2486,7 @@ test "if error union" {
     }
 
     // Access the value by reference using a pointer capture.
-    var c: %u32 = 3;
+    var c: error!u32 = 3;
     if (c) |*value| {
         *value = 9;
     } else |err| {
@@ -2558,8 +2554,7 @@ test "defer unwinding" {
 //
 // This is especially useful in allowing a function to clean up properly
 // on error, and replaces goto error handling tactics as seen in c.
-error DeferError;
-fn deferErrorExample(is_error: bool) %void {
+fn deferErrorExample(is_error: bool) !void {
     warn("\nstart of function\n");
 
     // This will always be executed on exit
@@ -2668,7 +2663,7 @@ test "foo" {
     assert(value == 1234);
 }
 
-fn bar() %u32 {
+fn bar() error!u32 {
     return 1234;
 }
 
@@ -2791,13 +2786,8 @@ test "fn reflection" {
       One of the distinguishing features of Zig is its exception handling strategy.
       

- Among the top level declarations available is the error value declaration: + TODO rewrite the errors section to take into account error sets

- {#code_begin|syntax#} -error FileNotFound; -error OutOfMemory; -error UnexpectedToken; - {#code_end#}

These error values are assigned an unsigned integer value greater than 0 at compile time. You are allowed to declare the same error value more than once, @@ -2809,26 +2799,23 @@ error UnexpectedToken;

Each error value across the entire compilation unit gets a unique integer, - and this determines the size of the pure error type. + and this determines the size of the error set type.

- The pure error type is one of the error values, and in the same way that pointers - cannot be null, a pure error is always an error. + The error set type is one of the error values, and in the same way that pointers + cannot be null, a error set instance is always an error.

{#code_begin|syntax#}const pure_error = error.FileNotFound;{#code_end#}

- Most of the time you will not find yourself using a pure error type. Instead, - likely you will be using the error union type. This is when you take a normal type, - and prefix it with the % operator. + Most of the time you will not find yourself using an error set type. Instead, + likely you will be using the error union type. This is when you take an error set + and a normal type, and create an error union with the ! binary operator.

Here is a function to parse a string into a 64-bit integer:

{#code_begin|test#} -error InvalidChar; -error Overflow; - -pub fn parseU64(buf: []const u8, radix: u8) %u64 { +pub fn parseU64(buf: []const u8, radix: u8) !u64 { var x: u64 = 0; for (buf) |c| { @@ -2867,13 +2854,14 @@ test "parse u64" { } {#code_end#}

- Notice the return type is %u64. This means that the function - either returns an unsigned 64 bit integer, or an error. + Notice the return type is !u64. This means that the function + either returns an unsigned 64 bit integer, or an error. We left off the error set + to the left of the !, so the error set is inferred.

Within the function definition, you can see some return statements that return - a pure error, and at the bottom a return statement that returns a u64. - Both types implicitly cast to %u64. + an error, and at the bottom a return statement that returns a u64. + Both types implicitly cast to error!u64.

What it looks like to use this function varies depending on what you're @@ -2900,7 +2888,7 @@ fn doAThing(str: []u8) void {

Let's say you wanted to return the error if you got one, otherwise continue with the function logic:

{#code_begin|syntax#} -fn doAThing(str: []u8) %void { +fn doAThing(str: []u8) !void { const number = parseU64(str, 10) catch |err| return err; // ... } @@ -2909,7 +2897,7 @@ fn doAThing(str: []u8) %void { There is a shortcut for this. The try expression:

{#code_begin|syntax#} -fn doAThing(str: []u8) %void { +fn doAThing(str: []u8) !void { const number = try parseU64(str, 10); // ... } @@ -2959,7 +2947,7 @@ fn doAThing(str: []u8) void { Example:

{#code_begin|syntax#} -fn createFoo(param: i32) %Foo { +fn createFoo(param: i32) !Foo { const foo = try tryToAllocateFoo(); // now we have allocated foo. we need to free it if the function fails. // but we want to return it if the function succeeds. @@ -2999,15 +2987,13 @@ fn createFoo(param: i32) %Foo { {#see_also|defer|if|switch#} {#header_open|Error Union Type#} -

An error union is created by putting a % in front of a type. +

An error union is created with the ! binary operator. You can use compile-time reflection to access the child type of an error union:

{#code_begin|test#} const assert = @import("std").debug.assert; -error SomeError; - test "error union" { - var foo: %i32 = undefined; + var foo: error!i32 = undefined; // Implicitly cast from child type of an error union: foo = 1234; @@ -3015,8 +3001,11 @@ test "error union" { // Implicitly cast from an error set: foo = error.SomeError; - // Use compile-time reflection to access the child type of an error union: - comptime assert(@typeOf(foo).Child == i32); + // Use compile-time reflection to access the payload type of an error union: + comptime assert(@typeOf(foo).Payload == i32); + + // Use compile-time reflection to access the error set type of an error union: + comptime assert(@typeOf(foo).ErrorSet == error); } {#code_end#} {#header_close#} @@ -3610,7 +3599,7 @@ pub fn main() void { {#code_begin|syntax#} /// Calls print and then flushes the buffer. -pub fn printf(self: &OutStream, comptime format: []const u8, args: ...) %void { +pub fn printf(self: &OutStream, comptime format: []const u8, args: ...) error!void { const State = enum { Start, OpenBrace, @@ -3682,7 +3671,7 @@ pub fn printf(self: &OutStream, comptime format: []const u8, args: ...) %void { and emits a function that actually looks like this:

{#code_begin|syntax#} -pub fn printf(self: &OutStream, arg0: i32, arg1: []const u8) %void { +pub fn printf(self: &OutStream, arg0: i32, arg1: []const u8) !void { try self.write("here is a string: '"); try self.printValue(arg0); try self.write("' here is a number: "); @@ -3696,7 +3685,7 @@ pub fn printf(self: &OutStream, arg0: i32, arg1: []const u8) %void { on the type:

{#code_begin|syntax#} -pub fn printValue(self: &OutStream, value: var) %void { +pub fn printValue(self: &OutStream, value: var) !void { const T = @typeOf(value); if (@isInteger(T)) { return self.printInt(T, value); @@ -4647,7 +4636,7 @@ pub const TypeId = enum { {#code_begin|syntax#} const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) %void { +pub fn build(b: &Builder) void { const exe = b.addExecutable("example", "example.zig"); exe.setBuildMode(b.standardReleaseOptions()); b.default_step.dependOn(&exe.step); @@ -4789,7 +4778,7 @@ comptime { {#code_begin|exe_err#} const math = @import("std").math; const warn = @import("std").debug.warn; -pub fn main() %void { +pub fn main() !void { var byte: u8 = 255; byte = if (math.add(u8, byte, 1)) |result| result else |err| { @@ -4817,7 +4806,7 @@ pub fn main() %void {

{#code_begin|exe#} const warn = @import("std").debug.warn; -pub fn main() %void { +pub fn main() void { var byte: u8 = 255; var result: u8 = undefined; @@ -4926,14 +4915,12 @@ pub fn main() void { {#header_close#} {#header_open|Attempt to Unwrap Error#}

At compile-time:

- {#code_begin|test_err|unable to unwrap error 'UnableToReturnNumber'#} + {#code_begin|test_err|caught unexpected error 'UnableToReturnNumber'#} comptime { const number = getNumberOrFail() catch unreachable; } -error UnableToReturnNumber; - -fn getNumberOrFail() %i32 { +fn getNumberOrFail() !i32 { return error.UnableToReturnNumber; } {#code_end#} @@ -4953,9 +4940,7 @@ pub fn main() void { } } -error UnableToReturnNumber; - -fn getNumberOrFail() %i32 { +fn getNumberOrFail() !i32 { return error.UnableToReturnNumber; } {#code_end#} @@ -4963,7 +4948,6 @@ fn getNumberOrFail() %i32 { {#header_open|Invalid Error Code#}

At compile-time:

{#code_begin|test_err|integer value 11 represents no error#} -error AnError; comptime { const err = error.AnError; const number = u32(err) + 10; @@ -5363,7 +5347,7 @@ int main(int argc, char **argv) { {#code_begin|syntax#} const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) %void { +pub fn build(b: &Builder) void { const obj = b.addObject("base64", "base64.zig"); const exe = b.addCExecutable("test"); @@ -5641,14 +5625,12 @@ fn readU32Be() u32 {} {#header_open|Grammar#}
Root = many(TopLevelItem) EOF
 
-TopLevelItem = ErrorValueDecl | CompTimeExpression(Block) | TopLevelDecl | TestDecl
+TopLevelItem = CompTimeExpression(Block) | TopLevelDecl | TestDecl
 
 TestDecl = "test" String Block
 
 TopLevelDecl = option("pub") (FnDef | ExternDecl | GlobalVarDecl | UseDecl)
 
-ErrorValueDecl = "error" Symbol ";"
-
 GlobalVarDecl = option("export") VariableDeclaration ";"
 
 LocalVarDecl = option("comptime") VariableDeclaration
@@ -5663,7 +5645,7 @@ UseDecl = "use" Expression ";"
 
 ExternDecl = "extern" option(String) (FnProto | VariableDeclaration) ";"
 
-FnProto = option("nakedcc" | "stdcallcc" | "extern") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") TypeExpr
+FnProto = option("nakedcc" | "stdcallcc" | "extern") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("!") TypeExpr
 
 FnDef = option("inline" | "export") FnProto Block
 
@@ -5675,7 +5657,9 @@ Block = option(Symbol ":") "{" many(Statement) "}"
 
 Statement = LocalVarDecl ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";"
 
-TypeExpr = PrefixOpExpression | "var"
+TypeExpr = ErrorSetExpr | "var"
+
+ErrorSetExpr = (PrefixOpExpression "!" PrefixOpExpression) | PrefixOpExpression
 
 BlockOrExpression = Block | Expression
 
@@ -5757,9 +5741,9 @@ MultiplyExpression = CurlySuffixExpression MultiplyOperator MultiplyExpression |
 
 CurlySuffixExpression = TypeExpr option(ContainerInitExpression)
 
-MultiplyOperator = "*" | "/" | "%" | "**" | "*%"
+MultiplyOperator = "||" | "*" | "/" | "%" | "**" | "*%"
 
-PrefixOpExpression = PrefixOp PrefixOpExpression | SuffixOpExpression
+PrefixOpExpression = PrefixOp ErrorSetExpr | SuffixOpExpression
 
 SuffixOpExpression = PrimaryExpression option(FnCallExpression | ArrayAccessExpression | FieldAccessExpression | SliceExpression)
 
@@ -5777,9 +5761,9 @@ ContainerInitBody = list(StructLiteralField, ",") | list(Expression, ",")
 
 StructLiteralField = "." Symbol "=" Expression
 
-PrefixOp = "!" | "-" | "~" | "*" | ("&" option("align" "(" Expression option(":" Integer ":" Integer) ")" ) option("const") option("volatile")) | "?" | "%" | "??" | "-%" | "try"
+PrefixOp = "!" | "-" | "~" | "*" | ("&" option("align" "(" Expression option(":" Integer ":" Integer) ")" ) option("const") option("volatile")) | "?" | "??" | "-%" | "try"
 
-PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." Symbol) | ContainerDecl | ("continue" option(":" Symbol))
+PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ContainerDecl | ("continue" option(":" Symbol)) | ErrorSetDecl
 
 ArrayType : "[" option(Expression) "]" option("align" "(" Expression option(":" Integer ":" Integer) ")")) option("const") option("volatile") TypeExpr
 
@@ -5787,6 +5771,8 @@ GroupedExpression = "(" Expression ")"
 
 KeywordLiteral = "true" | "false" | "null" | "undefined" | "error" | "this" | "unreachable"
 
+ErrorSetDecl = "error" "{" list(Symbol, ",") "}"
+
 ContainerDecl = option("extern" | "packed")
   ("struct" option(GroupedExpression) | "union" option("enum" option(GroupedExpression) | GroupedExpression) | ("enum" option(GroupedExpression)))
   "{" many(ContainerMember) "}"
diff --git a/example/cat/main.zig b/example/cat/main.zig index f519ca3f18..de0d323bed 100644 --- a/example/cat/main.zig +++ b/example/cat/main.zig @@ -5,7 +5,7 @@ const os = std.os; const warn = std.debug.warn; const allocator = std.debug.global_allocator; -pub fn main() %void { +pub fn main() !void { var args_it = os.args(); const exe = try unwrapArg(??args_it.next(allocator)); var catted_anything = false; @@ -20,7 +20,7 @@ pub fn main() %void { } else if (arg[0] == '-') { return usage(exe); } else { - var file = io.File.openRead(arg, null) catch |err| { + var file = os.File.openRead(allocator, arg) catch |err| { warn("Unable to open file: {}\n", @errorName(err)); return err; }; @@ -36,12 +36,12 @@ pub fn main() %void { } } -fn usage(exe: []const u8) %void { +fn usage(exe: []const u8) !void { warn("Usage: {} [FILE]...\n", exe); return error.Invalid; } -fn cat_file(stdout: &io.File, file: &io.File) %void { +fn cat_file(stdout: &os.File, file: &os.File) !void { var buf: [1024 * 4]u8 = undefined; while (true) { @@ -61,7 +61,7 @@ fn cat_file(stdout: &io.File, file: &io.File) %void { } } -fn unwrapArg(arg: %[]u8) %[]u8 { +fn unwrapArg(arg: error![]u8) ![]u8 { return arg catch |err| { warn("Unable to parse command line: {}\n", err); return err; diff --git a/example/guess_number/main.zig b/example/guess_number/main.zig index 482fb2f61e..d6a7b94b6c 100644 --- a/example/guess_number/main.zig +++ b/example/guess_number/main.zig @@ -5,7 +5,7 @@ const fmt = std.fmt; const Rand = std.rand.Rand; const os = std.os; -pub fn main() %void { +pub fn main() !void { var stdout_file = try io.getStdOut(); var stdout_file_stream = io.FileOutStream.init(&stdout_file); const stdout = &stdout_file_stream.stream; diff --git a/example/hello_world/hello.zig b/example/hello_world/hello.zig index 9fd050d701..8e65e06a96 100644 --- a/example/hello_world/hello.zig +++ b/example/hello_world/hello.zig @@ -1,6 +1,6 @@ const std = @import("std"); -pub fn main() %void { +pub fn main() !void { // If this program is run without stdout attached, exit with an error. var stdout_file = try std.io.getStdOut(); // If this program encounters pipe failure when printing to stdout, exit diff --git a/example/mix_o_files/base64.zig b/example/mix_o_files/base64.zig index 553c780b98..e682a97055 100644 --- a/example/mix_o_files/base64.zig +++ b/example/mix_o_files/base64.zig @@ -8,3 +8,6 @@ export fn decode_base_64(dest_ptr: &u8, dest_len: usize, source_ptr: &const u8, base64_decoder.decode(dest[0..decoded_size], src); return decoded_size; } + +var x: c_int = 1234; +export var x_ptr = &x; diff --git a/example/mix_o_files/build.zig b/example/mix_o_files/build.zig index 25d0fdd2ff..4380486867 100644 --- a/example/mix_o_files/build.zig +++ b/example/mix_o_files/build.zig @@ -1,6 +1,6 @@ const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) %void { +pub fn build(b: &Builder) void { const obj = b.addObject("base64", "base64.zig"); const exe = b.addCExecutable("test"); diff --git a/example/mix_o_files/test.c b/example/mix_o_files/test.c index 8a4758acc4..d821bbe108 100644 --- a/example/mix_o_files/test.c +++ b/example/mix_o_files/test.c @@ -4,6 +4,8 @@ #include #include +extern int *x_ptr; + int main(int argc, char **argv) { const char *encoded = "YWxsIHlvdXIgYmFzZSBhcmUgYmVsb25nIHRvIHVz"; char buf[200]; @@ -12,5 +14,7 @@ int main(int argc, char **argv) { buf[len] = 0; assert(strcmp(buf, "all your base are belong to us") == 0); + assert(*x_ptr == 1234); + return 0; } diff --git a/example/shared_library/build.zig b/example/shared_library/build.zig index 52d60da819..2b5a178b35 100644 --- a/example/shared_library/build.zig +++ b/example/shared_library/build.zig @@ -1,6 +1,6 @@ const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) %void { +pub fn build(b: &Builder) void { const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0)); const exe = b.addCExecutable("test"); diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 7f9e5073a5..4d59783098 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -14,21 +14,8 @@ const builtin = @import("builtin"); const ArrayList = std.ArrayList; const c = @import("c.zig"); -error InvalidCommandLineArguments; -error ZigLibDirNotFound; -error ZigInstallationNotFound; - const default_zig_cache_name = "zig-cache"; -pub fn main() %void { - main2() catch |err| { - if (err != error.InvalidCommandLineArguments) { - warn("{}\n", @errorName(err)); - } - return err; - }; -} - const Cmd = enum { None, Build, @@ -39,21 +26,25 @@ const Cmd = enum { Targets, }; -fn badArgs(comptime format: []const u8, args: ...) error { - var stderr = try io.getStdErr(); +fn badArgs(comptime format: []const u8, args: ...) noreturn { + var stderr = io.getStdErr() catch std.os.exit(1); var stderr_stream_adapter = io.FileOutStream.init(&stderr); const stderr_stream = &stderr_stream_adapter.stream; - try stderr_stream.print(format ++ "\n\n", args); - try printUsage(&stderr_stream_adapter.stream); - return error.InvalidCommandLineArguments; + stderr_stream.print(format ++ "\n\n", args) catch std.os.exit(1); + printUsage(&stderr_stream_adapter.stream) catch std.os.exit(1); + std.os.exit(1); } -pub fn main2() %void { +pub fn main() !void { const allocator = std.heap.c_allocator; const args = try os.argsAlloc(allocator); defer os.argsFree(allocator, args); + if (args.len >= 2 and mem.eql(u8, args[1], "fmt")) { + return fmtMain(allocator, args[2..]); + } + var cmd = Cmd.None; var build_kind: Module.Kind = undefined; var build_mode: builtin.Mode = builtin.Mode.Debug; @@ -173,7 +164,7 @@ pub fn main2() %void { } else if (mem.eql(u8, arg, "--pkg-end")) { @panic("TODO --pkg-end"); } else if (arg_i + 1 >= args.len) { - return badArgs("expected another argument after {}", arg); + badArgs("expected another argument after {}", arg); } else { arg_i += 1; if (mem.eql(u8, arg, "--output")) { @@ -188,7 +179,7 @@ pub fn main2() %void { } else if (mem.eql(u8, args[arg_i], "off")) { color = ErrColor.Off; } else { - return badArgs("--color options are 'auto', 'on', or 'off'"); + badArgs("--color options are 'auto', 'on', or 'off'"); } } else if (mem.eql(u8, arg, "--emit")) { if (mem.eql(u8, args[arg_i], "asm")) { @@ -198,7 +189,7 @@ pub fn main2() %void { } else if (mem.eql(u8, args[arg_i], "llvm-ir")) { emit_file_type = Emit.LlvmIr; } else { - return badArgs("--emit options are 'asm', 'bin', or 'llvm-ir'"); + badArgs("--emit options are 'asm', 'bin', or 'llvm-ir'"); } } else if (mem.eql(u8, arg, "--name")) { out_name_arg = args[arg_i]; @@ -266,7 +257,7 @@ pub fn main2() %void { } else if (mem.eql(u8, arg, "--test-cmd")) { @panic("TODO --test-cmd"); } else { - return badArgs("invalid argument: {}", arg); + badArgs("invalid argument: {}", arg); } } } else if (cmd == Cmd.None) { @@ -289,18 +280,18 @@ pub fn main2() %void { cmd = Cmd.Test; build_kind = Module.Kind.Exe; } else { - return badArgs("unrecognized command: {}", arg); + badArgs("unrecognized command: {}", arg); } } else switch (cmd) { Cmd.Build, Cmd.TranslateC, Cmd.Test => { if (in_file_arg == null) { in_file_arg = arg; } else { - return badArgs("unexpected extra parameter: {}", arg); + badArgs("unexpected extra parameter: {}", arg); } }, Cmd.Version, Cmd.Zen, Cmd.Targets => { - return badArgs("unexpected extra parameter: {}", arg); + badArgs("unexpected extra parameter: {}", arg); }, Cmd.None => unreachable, } @@ -337,15 +328,15 @@ pub fn main2() %void { // } switch (cmd) { - Cmd.None => return badArgs("expected command"), + Cmd.None => badArgs("expected command"), Cmd.Zen => return printZen(), Cmd.Build, Cmd.Test, Cmd.TranslateC => { if (cmd == Cmd.Build and in_file_arg == null and objects.len == 0 and asm_files.len == 0) { - return badArgs("expected source file argument or at least one --object or --assembly argument"); + badArgs("expected source file argument or at least one --object or --assembly argument"); } else if ((cmd == Cmd.TranslateC or cmd == Cmd.Test) and in_file_arg == null) { - return badArgs("expected source file argument"); + badArgs("expected source file argument"); } else if (cmd == Cmd.Build and build_kind == Module.Kind.Obj and objects.len != 0) { - return badArgs("When building an object file, --object arguments are invalid"); + badArgs("When building an object file, --object arguments are invalid"); } const root_name = switch (cmd) { @@ -355,9 +346,9 @@ pub fn main2() %void { } else if (in_file_arg) |in_file_path| { const basename = os.path.basename(in_file_path); var it = mem.split(basename, "."); - break :x it.next() ?? return badArgs("file name cannot be empty"); + break :x it.next() ?? badArgs("file name cannot be empty"); } else { - return badArgs("--name [name] not provided and unable to infer"); + badArgs("--name [name] not provided and unable to infer"); } }, Cmd.Test => "test", @@ -432,7 +423,7 @@ pub fn main2() %void { module.linker_rdynamic = rdynamic; if (mmacosx_version_min != null and mios_version_min != null) { - return badArgs("-mmacosx-version-min and -mios-version-min options not allowed together"); + badArgs("-mmacosx-version-min and -mios-version-min options not allowed together"); } if (mmacosx_version_min) |ver| { @@ -472,7 +463,7 @@ pub fn main2() %void { } } -fn printUsage(stream: &io.OutStream) %void { +fn printUsage(stream: var) !void { try stream.write( \\Usage: zig [command] [options] \\ @@ -481,6 +472,7 @@ fn printUsage(stream: &io.OutStream) %void { \\ build-exe [source] create executable from source or object files \\ build-lib [source] create library from source or object files \\ build-obj [source] create object from source or assembly + \\ fmt [file] parse file and render in canonical zig format \\ translate-c [source] convert c code to zig code \\ targets list available compilation targets \\ test [source] create and run a test build @@ -548,7 +540,7 @@ fn printUsage(stream: &io.OutStream) %void { ); } -fn printZen() %void { +fn printZen() !void { var stdout_file = try io.getStdErr(); try stdout_file.write( \\ @@ -568,8 +560,33 @@ fn printZen() %void { ); } +fn fmtMain(allocator: &mem.Allocator, file_paths: []const []const u8) !void { + for (file_paths) |file_path| { + var file = try os.File.openRead(allocator, file_path); + defer file.close(); + + const source_code = io.readFileAlloc(allocator, file_path) catch |err| { + warn("unable to open '{}': {}", file_path, err); + continue; + }; + defer allocator.free(source_code); + + var tokenizer = std.zig.Tokenizer.init(source_code); + var parser = std.zig.Parser.init(&tokenizer, allocator, file_path); + defer parser.deinit(); + + const tree = try parser.parse(); + defer tree.deinit(); + + const baf = try io.BufferedAtomicFile.create(allocator, file_path); + defer baf.destroy(); + + try parser.renderSource(baf.stream(), tree.root_node); + } +} + /// Caller must free result -fn resolveZigLibDir(allocator: &mem.Allocator, zig_install_prefix_arg: ?[]const u8) %[]u8 { +fn resolveZigLibDir(allocator: &mem.Allocator, zig_install_prefix_arg: ?[]const u8) ![]u8 { if (zig_install_prefix_arg) |zig_install_prefix| { return testZigInstallPrefix(allocator, zig_install_prefix) catch |err| { warn("No Zig installation found at prefix {}: {}\n", zig_install_prefix_arg, @errorName(err)); @@ -585,21 +602,21 @@ fn resolveZigLibDir(allocator: &mem.Allocator, zig_install_prefix_arg: ?[]const } /// Caller must free result -fn testZigInstallPrefix(allocator: &mem.Allocator, test_path: []const u8) %[]u8 { +fn testZigInstallPrefix(allocator: &mem.Allocator, test_path: []const u8) ![]u8 { const test_zig_dir = try os.path.join(allocator, test_path, "lib", "zig"); errdefer allocator.free(test_zig_dir); const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig"); defer allocator.free(test_index_file); - var file = try io.File.openRead(test_index_file, allocator); + var file = try os.File.openRead(allocator, test_index_file); file.close(); return test_zig_dir; } /// Caller must free result -fn findZigLibDir(allocator: &mem.Allocator) %[]u8 { +fn findZigLibDir(allocator: &mem.Allocator) ![]u8 { const self_exe_path = try os.selfExeDirPath(allocator); defer allocator.free(self_exe_path); @@ -626,8 +643,3 @@ fn findZigLibDir(allocator: &mem.Allocator) %[]u8 { return error.FileNotFound; } - -test "import tests" { - _ = @import("tokenizer.zig"); - _ = @import("parser.zig"); -} diff --git a/src-self-hosted/module.zig b/src-self-hosted/module.zig index f7bf6e0de7..43bba22757 100644 --- a/src-self-hosted/module.zig +++ b/src-self-hosted/module.zig @@ -8,9 +8,9 @@ const c = @import("c.zig"); const builtin = @import("builtin"); const Target = @import("target.zig").Target; const warn = std.debug.warn; -const Tokenizer = @import("tokenizer.zig").Tokenizer; -const Token = @import("tokenizer.zig").Token; -const Parser = @import("parser.zig").Parser; +const Tokenizer = std.zig.Tokenizer; +const Token = std.zig.Token; +const Parser = std.zig.Parser; const ArrayList = std.ArrayList; pub const Module = struct { @@ -110,7 +110,7 @@ pub const Module = struct { }; pub fn create(allocator: &mem.Allocator, name: []const u8, root_src_path: ?[]const u8, target: &const Target, - kind: Kind, build_mode: builtin.Mode, zig_lib_dir: []const u8, cache_dir: []const u8) %&Module + kind: Kind, build_mode: builtin.Mode, zig_lib_dir: []const u8, cache_dir: []const u8) !&Module { var name_buffer = try Buffer.init(allocator, name); errdefer name_buffer.deinit(); @@ -198,7 +198,7 @@ pub const Module = struct { self.allocator.destroy(self); } - pub fn build(self: &Module) %void { + pub fn build(self: &Module) !void { if (self.llvm_argv.len != 0) { var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(self.allocator, [][]const []const u8 { [][]const u8{"zig (LLVM option parsing)"}, self.llvm_argv, }); @@ -213,14 +213,11 @@ pub const Module = struct { }; errdefer self.allocator.free(root_src_real_path); - const source_code = io.readFileAllocExtra(root_src_real_path, self.allocator, 3) catch |err| { + const source_code = io.readFileAlloc(self.allocator, root_src_real_path) catch |err| { try printError("unable to open '{}': {}", root_src_real_path, err); return err; }; errdefer self.allocator.free(source_code); - source_code[source_code.len - 3] = '\n'; - source_code[source_code.len - 2] = '\n'; - source_code[source_code.len - 1] = '\n'; warn("====input:====\n"); @@ -263,11 +260,12 @@ pub const Module = struct { } - pub fn link(self: &Module, out_file: ?[]const u8) %void { + pub fn link(self: &Module, out_file: ?[]const u8) !void { warn("TODO link"); + return error.Todo; } - pub fn addLinkLib(self: &Module, name: []const u8, provided_explicitly: bool) %&LinkLib { + pub fn addLinkLib(self: &Module, name: []const u8, provided_explicitly: bool) !&LinkLib { const is_libc = mem.eql(u8, name, "c"); if (is_libc) { @@ -297,7 +295,7 @@ pub const Module = struct { } }; -fn printError(comptime format: []const u8, args: ...) %void { +fn printError(comptime format: []const u8, args: ...) !void { var stderr_file = try std.io.getStdErr(); var stderr_file_out_stream = std.io.FileOutStream.init(&stderr_file); const out_stream = &stderr_file_out_stream.stream; diff --git a/src/all_types.hpp b/src/all_types.hpp index 0f41760718..04b781a598 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -236,7 +236,7 @@ struct ConstExprValue { TypeTableEntry *x_type; ConstExprValue *x_maybe; ConstErrValue x_err_union; - ErrorTableEntry *x_pure_err; + ErrorTableEntry *x_err_set; BigInt x_enum_tag; ConstStructValue x_struct; ConstUnionValue x_union; @@ -353,7 +353,6 @@ enum NodeType { NodeTypeReturnExpr, NodeTypeDefer, NodeTypeVariableDeclaration, - NodeTypeErrorValueDecl, NodeTypeTestDecl, NodeTypeBinOpExpr, NodeTypeUnwrapErrorExpr, @@ -393,6 +392,7 @@ enum NodeType { NodeTypeVarLiteral, NodeTypeIfErrorExpr, NodeTypeTestExpr, + NodeTypeErrorSetDecl, }; struct AstNodeRoot { @@ -424,6 +424,8 @@ struct AstNodeFnProto { AstNode *align_expr; // populated if the "section(S)" is present AstNode *section_expr; + + bool auto_err_set; }; struct AstNodeFnDef { @@ -486,12 +488,6 @@ struct AstNodeVariableDeclaration { AstNode *section_expr; }; -struct AstNodeErrorValueDecl { - Buf *name; - - ErrorTableEntry *err; -}; - struct AstNodeTestDecl { Buf *name; @@ -514,8 +510,7 @@ enum BinOpType { BinOpTypeAssignBitAnd, BinOpTypeAssignBitXor, BinOpTypeAssignBitOr, - BinOpTypeAssignBoolAnd, - BinOpTypeAssignBoolOr, + BinOpTypeAssignMergeErrorSets, BinOpTypeBoolOr, BinOpTypeBoolAnd, BinOpTypeCmpEq, @@ -540,6 +535,8 @@ enum BinOpType { BinOpTypeUnwrapMaybe, BinOpTypeArrayCat, BinOpTypeArrayMult, + BinOpTypeErrorUnion, + BinOpTypeMergeErrorSets, }; struct AstNodeBinOpExpr { @@ -563,6 +560,7 @@ enum CastOp { CastOpResizeSlice, CastOpBytesToSlice, CastOpNumLitToConcrete, + CastOpErrSet, }; struct AstNodeFnCallExpr { @@ -595,7 +593,6 @@ enum PrefixOp { PrefixOpNegationWrap, PrefixOpDereference, PrefixOpMaybe, - PrefixOpError, PrefixOpUnwrapMaybe, }; @@ -762,6 +759,10 @@ struct AstNodeContainerDecl { bool auto_enum; // union(enum) }; +struct AstNodeErrorSetDecl { + ZigList decls; +}; + struct AstNodeStructField { VisibMod visib_mod; Buf *name; @@ -858,7 +859,6 @@ struct AstNode { AstNodeReturnExpr return_expr; AstNodeDefer defer; AstNodeVariableDeclaration variable_declaration; - AstNodeErrorValueDecl error_value_decl; AstNodeTestDecl test_decl; AstNodeBinOpExpr bin_op_expr; AstNodeCatchExpr unwrap_err_expr; @@ -899,6 +899,7 @@ struct AstNode { AstNodeArrayType array_type; AstNodeErrorType error_type; AstNodeVarLiteral var_literal; + AstNodeErrorSetDecl err_set_decl; } data; }; @@ -993,8 +994,15 @@ struct TypeTableEntryMaybe { TypeTableEntry *child_type; }; -struct TypeTableEntryError { - TypeTableEntry *child_type; +struct TypeTableEntryErrorUnion { + TypeTableEntry *err_set_type; + TypeTableEntry *payload_type; +}; + +struct TypeTableEntryErrorSet { + uint32_t err_count; + ErrorTableEntry **errors; + FnTableEntry *infer_fn; }; struct TypeTableEntryEnum { @@ -1097,7 +1105,7 @@ enum TypeTableEntryId { TypeTableEntryIdNullLit, TypeTableEntryIdMaybe, TypeTableEntryIdErrorUnion, - TypeTableEntryIdPureError, + TypeTableEntryIdErrorSet, TypeTableEntryIdEnum, TypeTableEntryIdUnion, TypeTableEntryIdFn, @@ -1126,7 +1134,8 @@ struct TypeTableEntry { TypeTableEntryArray array; TypeTableEntryStruct structure; TypeTableEntryMaybe maybe; - TypeTableEntryError error; + TypeTableEntryErrorUnion error_union; + TypeTableEntryErrorSet error_set; TypeTableEntryEnum enumeration; TypeTableEntryUnion unionation; TypeTableEntryFn fn; @@ -1136,7 +1145,6 @@ struct TypeTableEntry { // use these fields to make sure we don't duplicate type table entries for the same type TypeTableEntry *pointer_parent[2]; // [0 - mut, 1 - const] TypeTableEntry *maybe_parent; - TypeTableEntry *error_parent; // If we generate a constant name value for this type, we memoize it here. // The type of this is array ConstExprValue *cached_const_name_val; @@ -1340,6 +1348,10 @@ struct TypeId { bool is_signed; uint32_t bit_count; } integer; + struct { + TypeTableEntry *err_set_type; + TypeTableEntry *payload_type; + } error_union; } data; }; @@ -1481,7 +1493,7 @@ struct CodeGen { TypeTableEntry *entry_undef; TypeTableEntry *entry_null; TypeTableEntry *entry_var; - TypeTableEntry *entry_pure_error; + TypeTableEntry *entry_global_error_set; TypeTableEntry *entry_arg_tuple; } builtin_types; @@ -1570,7 +1582,6 @@ struct CodeGen { LLVMValueRef return_address_fn_val; LLVMValueRef frame_address_fn_val; bool error_during_imports; - TypeTableEntry *err_tag_type; const char **clang_argv; size_t clang_argv_len; @@ -1584,7 +1595,9 @@ struct CodeGen { bool each_lib_rpath; - ZigList error_decls; + TypeTableEntry *err_tag_type; + ZigList err_enumerators; + ZigList errors_by_index; bool generate_error_name_table; LLVMValueRef err_name_table; size_t largest_err_name_len; @@ -1617,6 +1630,10 @@ struct CodeGen { TypeTableEntry *align_amt_type; TypeTableEntry *stack_trace_type; TypeTableEntry *ptr_to_stack_trace_type; + + ZigList error_di_types; + + ZigList forbidden_libs; }; enum VarLinkage { @@ -1653,6 +1670,7 @@ struct ErrorTableEntry { Buf name; uint32_t value; AstNode *decl_node; + TypeTableEntry *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 ConstExprValue *cached_error_name_val; @@ -1920,6 +1938,7 @@ enum IrInstructionId { IrInstructionIdArgType, IrInstructionIdExport, IrInstructionIdErrorReturnTrace, + IrInstructionIdErrorUnion, }; struct IrInstruction { @@ -1996,7 +2015,6 @@ enum IrUnOp { IrUnOpNegation, IrUnOpNegationWrap, IrUnOpDereference, - IrUnOpError, IrUnOpMaybe, }; @@ -2039,6 +2057,7 @@ enum IrBinOp { IrBinOpRemMod, IrBinOpArrayCat, IrBinOpArrayMult, + IrBinOpMergeErrorSets, }; struct IrInstructionBinOp { @@ -2750,6 +2769,13 @@ struct IrInstructionErrorReturnTrace { IrInstruction base; }; +struct IrInstructionErrorUnion { + IrInstruction base; + + IrInstruction *err_set; + IrInstruction *payload; +}; + static const size_t slice_ptr_index = 0; static const size_t slice_len_index = 1; diff --git a/src/analyze.cpp b/src/analyze.cpp index 4c982c160c..bf7b6e363f 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -224,7 +224,7 @@ bool type_is_complete(TypeTableEntry *type_entry) { case TypeTableEntryIdNullLit: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdFn: case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: @@ -260,7 +260,7 @@ bool type_has_zero_bits_known(TypeTableEntry *type_entry) { case TypeTableEntryIdNullLit: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdFn: case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: @@ -514,29 +514,47 @@ TypeTableEntry *get_maybe_type(CodeGen *g, TypeTableEntry *child_type) { } } -TypeTableEntry *get_error_type(CodeGen *g, TypeTableEntry *child_type) { - if (child_type->error_parent) - return child_type->error_parent; +TypeTableEntry *get_error_union_type(CodeGen *g, TypeTableEntry *err_set_type, TypeTableEntry *payload_type) { + assert(err_set_type->id == TypeTableEntryIdErrorSet); + assert(!type_is_invalid(payload_type)); + + TypeId type_id = {}; + type_id.id = TypeTableEntryIdErrorUnion; + type_id.data.error_union.err_set_type = err_set_type; + type_id.data.error_union.payload_type = payload_type; + + auto existing_entry = g->type_table.maybe_get(type_id); + if (existing_entry) { + return existing_entry->value; + } TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdErrorUnion); entry->is_copyable = true; - assert(child_type->type_ref); - assert(child_type->di_type); - ensure_complete_type(g, child_type); + assert(payload_type->di_type); + ensure_complete_type(g, payload_type); buf_resize(&entry->name, 0); - buf_appendf(&entry->name, "%%%s", buf_ptr(&child_type->name)); + buf_appendf(&entry->name, "%s!%s", buf_ptr(&err_set_type->name), buf_ptr(&payload_type->name)); - entry->data.error.child_type = child_type; - - if (!type_has_bits(child_type)) { - entry->type_ref = g->err_tag_type->type_ref; - entry->di_type = g->err_tag_type->di_type; + entry->data.error_union.err_set_type = err_set_type; + entry->data.error_union.payload_type = payload_type; + if (!type_has_bits(payload_type)) { + if (type_has_bits(err_set_type)) { + entry->type_ref = err_set_type->type_ref; + entry->di_type = err_set_type->di_type; + g->error_di_types.append(&entry->di_type); + } else { + entry->zero_bits = true; + entry->di_type = g->builtin_types.entry_void->di_type; + } + } else if (!type_has_bits(err_set_type)) { + entry->type_ref = payload_type->type_ref; + entry->di_type = payload_type->di_type; } else { LLVMTypeRef elem_types[] = { - g->err_tag_type->type_ref, - child_type->type_ref, + err_set_type->type_ref, + payload_type->type_ref, }; entry->type_ref = LLVMStructType(elem_types, 2, false); @@ -547,12 +565,12 @@ TypeTableEntry *get_error_type(CodeGen *g, TypeTableEntry *child_type) { ZigLLVMTag_DW_structure_type(), buf_ptr(&entry->name), compile_unit_scope, di_file, line); - uint64_t tag_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, g->err_tag_type->type_ref); - uint64_t tag_debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, g->err_tag_type->type_ref); + uint64_t tag_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, err_set_type->type_ref); + uint64_t tag_debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, err_set_type->type_ref); uint64_t tag_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, entry->type_ref, err_union_err_index); - uint64_t value_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, child_type->type_ref); - uint64_t value_debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, child_type->type_ref); + uint64_t value_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, payload_type->type_ref); + uint64_t value_debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, payload_type->type_ref); uint64_t value_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, entry->type_ref, err_union_payload_index); @@ -565,13 +583,13 @@ TypeTableEntry *get_error_type(CodeGen *g, TypeTableEntry *child_type) { tag_debug_size_in_bits, tag_debug_align_in_bits, tag_offset_in_bits, - 0, child_type->di_type), + 0, err_set_type->di_type), ZigLLVMCreateDebugMemberType(g->dbuilder, ZigLLVMTypeToScope(entry->di_type), "value", di_file, line, value_debug_size_in_bits, value_debug_align_in_bits, value_offset_in_bits, - 0, child_type->di_type), + 0, payload_type->di_type), }; ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugStructType(g->dbuilder, @@ -587,7 +605,7 @@ TypeTableEntry *get_error_type(CodeGen *g, TypeTableEntry *child_type) { entry->di_type = replacement_di_type; } - child_type->error_parent = entry; + g->type_table.put(type_id, entry); return entry; } @@ -937,7 +955,7 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) { handle_is_ptr(fn_type_id->return_type); bool prefix_arg_error_return_trace = g->have_err_ret_tracing && (fn_type_id->return_type->id == TypeTableEntryIdErrorUnion || - fn_type_id->return_type->id == TypeTableEntryIdPureError); + fn_type_id->return_type->id == TypeTableEntryIdErrorSet); // +1 for maybe making the first argument the return value // +1 for maybe last argument the error return trace LLVMTypeRef *gen_param_types = allocate(2 + fn_type_id->param_count); @@ -1177,7 +1195,7 @@ static bool type_allowed_in_packed_struct(TypeTableEntry *type_entry) { case TypeTableEntryIdUndefLit: case TypeTableEntryIdNullLit: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: @@ -1218,7 +1236,7 @@ static bool type_allowed_in_extern(CodeGen *g, TypeTableEntry *type_entry) { case TypeTableEntryIdUndefLit: case TypeTableEntryIdNullLit: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: @@ -1263,7 +1281,23 @@ static bool type_allowed_in_extern(CodeGen *g, TypeTableEntry *type_entry) { zig_unreachable(); } -static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *child_scope) { +TypeTableEntry *get_auto_err_set_type(CodeGen *g, FnTableEntry *fn_entry) { + TypeTableEntry *err_set_type = new_type_table_entry(TypeTableEntryIdErrorSet); + buf_resize(&err_set_type->name, 0); + buf_appendf(&err_set_type->name, "@typeOf(%s).ReturnType.ErrorSet", buf_ptr(&fn_entry->symbol_name)); + err_set_type->is_copyable = true; + err_set_type->type_ref = g->builtin_types.entry_global_error_set->type_ref; + err_set_type->di_type = g->builtin_types.entry_global_error_set->di_type; + err_set_type->data.error_set.err_count = 0; + err_set_type->data.error_set.errors = nullptr; + err_set_type->data.error_set.infer_fn = fn_entry; + + g->error_di_types.append(&err_set_type->di_type); + + return err_set_type; +} + +static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *child_scope, FnTableEntry *fn_entry) { assert(proto_node->type == NodeTypeFnProto); AstNodeFnProto *fn_proto = &proto_node->data.fn_proto; @@ -1359,7 +1393,7 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c case TypeTableEntryIdStruct: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdEnum: case TypeTableEntryIdUnion: case TypeTableEntryIdFn: @@ -1382,13 +1416,19 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c } } - fn_type_id.return_type = (fn_proto->return_type == nullptr) ? - g->builtin_types.entry_void : analyze_type_expr(g, child_scope, fn_proto->return_type); - - if (type_is_invalid(fn_type_id.return_type)) { + TypeTableEntry *specified_return_type = analyze_type_expr(g, child_scope, fn_proto->return_type); + if (type_is_invalid(specified_return_type)) { + fn_type_id.return_type = g->builtin_types.entry_invalid; return g->builtin_types.entry_invalid; } + if (fn_proto->auto_err_set) { + TypeTableEntry *inferred_err_set_type = get_auto_err_set_type(g, fn_entry); + fn_type_id.return_type = get_error_union_type(g, inferred_err_set_type, specified_return_type); + } else { + fn_type_id.return_type = specified_return_type; + } + if (fn_type_id.cc != CallingConventionUnspecified && !type_allowed_in_extern(g, fn_type_id.return_type)) { add_node_error(g, fn_proto->return_type, buf_sprintf("return type '%s' not allowed in function with calling convention '%s'", @@ -1434,7 +1474,7 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c case TypeTableEntryIdStruct: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdEnum: case TypeTableEntryIdUnion: case TypeTableEntryIdFn: @@ -2756,7 +2796,8 @@ TypeTableEntry *get_test_fn_type(CodeGen *g) { return g->test_fn_type; FnTypeId fn_type_id = {0}; - fn_type_id.return_type = get_error_type(g, g->builtin_types.entry_void); + fn_type_id.return_type = get_error_union_type(g, g->builtin_types.entry_global_error_set, + g->builtin_types.entry_void); g->test_fn_type = get_fn_type(g, &fn_type_id); return g->test_fn_type; } @@ -2824,7 +2865,7 @@ static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) { Scope *child_scope = fn_table_entry->fndef_scope ? &fn_table_entry->fndef_scope->base : tld_fn->base.parent_scope; - fn_table_entry->type_entry = analyze_fn_type(g, source_node, child_scope); + fn_table_entry->type_entry = analyze_fn_type(g, source_node, child_scope, fn_table_entry); if (fn_proto->section_expr != nullptr) { if (fn_table_entry->body_node == nullptr) { @@ -2949,29 +2990,6 @@ static void preview_test_decl(CodeGen *g, AstNode *node, ScopeDecls *decls_scope g->resolve_queue.append(&tld_fn->base); } -static void preview_error_value_decl(CodeGen *g, AstNode *node) { - assert(node->type == NodeTypeErrorValueDecl); - - ErrorTableEntry *err = allocate(1); - - err->decl_node = node; - buf_init_from_buf(&err->name, node->data.error_value_decl.name); - - auto existing_entry = g->error_table.maybe_get(&err->name); - if (existing_entry) { - // duplicate error definitions allowed and they get the same value - err->value = existing_entry->value->value; - } else { - size_t error_value_count = g->error_decls.length; - assert((uint32_t)error_value_count < (((uint32_t)1) << (uint32_t)g->err_tag_type->data.integral.bit_count)); - err->value = (uint32_t)error_value_count; - g->error_decls.append(node); - g->error_table.put(&err->name, err); - } - - node->data.error_value_decl.err = err; -} - static void preview_comptime_decl(CodeGen *g, AstNode *node, ScopeDecls *decls_scope) { assert(node->type == NodeTypeCompTime); @@ -3045,10 +3063,6 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node) { import->use_decls.append(node); break; } - case NodeTypeErrorValueDecl: - // error value declarations do not depend on other top level decls - preview_error_value_decl(g, node); - break; case NodeTypeTestDecl: preview_test_decl(g, node, decls_scope); break; @@ -3097,6 +3111,7 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node) { case NodeTypeVarLiteral: case NodeTypeIfErrorExpr: case NodeTypeTestExpr: + case NodeTypeErrorSetDecl: zig_unreachable(); } } @@ -3147,7 +3162,7 @@ TypeTableEntry *validate_var_type(CodeGen *g, AstNode *source_node, TypeTableEnt case TypeTableEntryIdStruct: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdEnum: case TypeTableEntryIdUnion: case TypeTableEntryIdFn: @@ -3362,108 +3377,6 @@ void resolve_top_level_decl(CodeGen *g, Tld *tld, bool pointer_only, AstNode *so g->tld_ref_source_node_stack.pop(); } -bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTableEntry *actual_type) { - if (expected_type == actual_type) - return true; - - // pointer const - if (expected_type->id == TypeTableEntryIdPointer && - actual_type->id == TypeTableEntryIdPointer && - (!actual_type->data.pointer.is_const || expected_type->data.pointer.is_const) && - (!actual_type->data.pointer.is_volatile || expected_type->data.pointer.is_volatile) && - actual_type->data.pointer.bit_offset == expected_type->data.pointer.bit_offset && - actual_type->data.pointer.unaligned_bit_count == expected_type->data.pointer.unaligned_bit_count && - actual_type->data.pointer.alignment >= expected_type->data.pointer.alignment) - { - return types_match_const_cast_only(expected_type->data.pointer.child_type, - actual_type->data.pointer.child_type); - } - - // slice const - if (is_slice(expected_type) && is_slice(actual_type)) { - TypeTableEntry *actual_ptr_type = actual_type->data.structure.fields[slice_ptr_index].type_entry; - TypeTableEntry *expected_ptr_type = expected_type->data.structure.fields[slice_ptr_index].type_entry; - if ((!actual_ptr_type->data.pointer.is_const || expected_ptr_type->data.pointer.is_const) && - (!actual_ptr_type->data.pointer.is_volatile || expected_ptr_type->data.pointer.is_volatile) && - actual_ptr_type->data.pointer.bit_offset == expected_ptr_type->data.pointer.bit_offset && - actual_ptr_type->data.pointer.unaligned_bit_count == expected_ptr_type->data.pointer.unaligned_bit_count && - actual_ptr_type->data.pointer.alignment >= expected_ptr_type->data.pointer.alignment) - { - return types_match_const_cast_only(expected_ptr_type->data.pointer.child_type, - actual_ptr_type->data.pointer.child_type); - } - } - - // maybe - if (expected_type->id == TypeTableEntryIdMaybe && - actual_type->id == TypeTableEntryIdMaybe) - { - return types_match_const_cast_only( - expected_type->data.maybe.child_type, - actual_type->data.maybe.child_type); - } - - // error - if (expected_type->id == TypeTableEntryIdErrorUnion && - actual_type->id == TypeTableEntryIdErrorUnion) - { - return types_match_const_cast_only( - expected_type->data.error.child_type, - actual_type->data.error.child_type); - } - - // fn - if (expected_type->id == TypeTableEntryIdFn && - actual_type->id == TypeTableEntryIdFn) - { - if (expected_type->data.fn.fn_type_id.alignment > actual_type->data.fn.fn_type_id.alignment) { - return false; - } - if (expected_type->data.fn.fn_type_id.cc != actual_type->data.fn.fn_type_id.cc) { - return false; - } - if (expected_type->data.fn.fn_type_id.is_var_args != actual_type->data.fn.fn_type_id.is_var_args) { - return false; - } - if (expected_type->data.fn.is_generic != actual_type->data.fn.is_generic) { - return false; - } - if (!expected_type->data.fn.is_generic && - actual_type->data.fn.fn_type_id.return_type->id != TypeTableEntryIdUnreachable && - !types_match_const_cast_only( - expected_type->data.fn.fn_type_id.return_type, - actual_type->data.fn.fn_type_id.return_type)) - { - return false; - } - if (expected_type->data.fn.fn_type_id.param_count != actual_type->data.fn.fn_type_id.param_count) { - return false; - } - if (expected_type->data.fn.fn_type_id.next_param_index != actual_type->data.fn.fn_type_id.next_param_index) { - return false; - } - assert(expected_type->data.fn.is_generic || - expected_type->data.fn.fn_type_id.next_param_index == expected_type->data.fn.fn_type_id.param_count); - for (size_t i = 0; i < expected_type->data.fn.fn_type_id.next_param_index; i += 1) { - // note it's reversed for parameters - FnTypeParamInfo *actual_param_info = &actual_type->data.fn.fn_type_id.param_info[i]; - FnTypeParamInfo *expected_param_info = &expected_type->data.fn.fn_type_id.param_info[i]; - - if (!types_match_const_cast_only(actual_param_info->type, expected_param_info->type)) { - return false; - } - - if (expected_param_info->is_noalias != actual_param_info->is_noalias) { - return false; - } - } - return true; - } - - - return false; -} - Tld *find_decl(CodeGen *g, Scope *scope, Buf *name) { // we must resolve all the use decls ImportTableEntry *import = get_scope_import(scope); @@ -3625,7 +3538,7 @@ static bool is_container(TypeTableEntry *type_entry) { case TypeTableEntryIdNullLit: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdFn: case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: @@ -3673,7 +3586,7 @@ void resolve_container_type(CodeGen *g, TypeTableEntry *type_entry) { case TypeTableEntryIdNullLit: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdFn: case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: @@ -3765,6 +3678,27 @@ void define_local_param_variables(CodeGen *g, FnTableEntry *fn_table_entry, Vari } } +static bool analyze_resolve_inferred_error_set(CodeGen *g, TypeTableEntry *err_set_type, AstNode *source_node) { + FnTableEntry *infer_fn = err_set_type->data.error_set.infer_fn; + if (infer_fn != nullptr) { + if (infer_fn->anal_state == FnAnalStateInvalid) { + return false; + } else if (infer_fn->anal_state == FnAnalStateReady) { + analyze_fn_body(g, infer_fn); + if (err_set_type->data.error_set.infer_fn != nullptr) { + assert(g->errors.length != 0); + return false; + } + } else { + add_node_error(g, source_node, + buf_sprintf("cannot resolve inferred error set '%s': function '%s' not fully analyzed yet", + buf_ptr(&err_set_type->name), buf_ptr(&err_set_type->data.error_set.infer_fn->symbol_name))); + return false; + } + } + return true; +} + void analyze_fn_ir(CodeGen *g, FnTableEntry *fn_table_entry, AstNode *return_type_node) { TypeTableEntry *fn_type = fn_table_entry->type_entry; assert(!fn_type->data.fn.is_generic); @@ -3774,14 +3708,49 @@ void analyze_fn_ir(CodeGen *g, FnTableEntry *fn_table_entry, AstNode *return_typ &fn_table_entry->analyzed_executable, fn_type_id->return_type, return_type_node); fn_table_entry->implicit_return_type = block_return_type; - if (block_return_type->id == TypeTableEntryIdInvalid || - fn_table_entry->analyzed_executable.invalid) - { + if (type_is_invalid(block_return_type) || fn_table_entry->analyzed_executable.invalid) { assert(g->errors.length > 0); fn_table_entry->anal_state = FnAnalStateInvalid; return; } + if (fn_type_id->return_type->id == TypeTableEntryIdErrorUnion) { + TypeTableEntry *return_err_set_type = fn_type_id->return_type->data.error_union.err_set_type; + if (return_err_set_type->data.error_set.infer_fn != nullptr) { + TypeTableEntry *inferred_err_set_type; + if (fn_table_entry->implicit_return_type->id == TypeTableEntryIdErrorSet) { + inferred_err_set_type = fn_table_entry->implicit_return_type; + } else if (fn_table_entry->implicit_return_type->id == TypeTableEntryIdErrorUnion) { + inferred_err_set_type = fn_table_entry->implicit_return_type->data.error_union.err_set_type; + } else { + add_node_error(g, return_type_node, + buf_sprintf("function with inferred error set must return at least one possible error")); + fn_table_entry->anal_state = FnAnalStateInvalid; + return; + } + + if (inferred_err_set_type->data.error_set.infer_fn != nullptr) { + if (!analyze_resolve_inferred_error_set(g, inferred_err_set_type, return_type_node)) { + fn_table_entry->anal_state = FnAnalStateInvalid; + return; + } + } + + return_err_set_type->data.error_set.infer_fn = nullptr; + if (type_is_global_error_set(inferred_err_set_type)) { + return_err_set_type->data.error_set.err_count = UINT32_MAX; + } else { + return_err_set_type->data.error_set.err_count = inferred_err_set_type->data.error_set.err_count; + if (inferred_err_set_type->data.error_set.err_count > 0) { + return_err_set_type->data.error_set.errors = allocate(inferred_err_set_type->data.error_set.err_count); + for (uint32_t i = 0; i < inferred_err_set_type->data.error_set.err_count; i += 1) { + return_err_set_type->data.error_set.errors[i] = inferred_err_set_type->data.error_set.errors[i]; + } + } + } + } + } + if (g->verbose_ir) { fprintf(stderr, "{ // (analyzed)\n"); ir_print(g, stderr, &fn_table_entry->analyzed_executable, 4); @@ -3791,7 +3760,7 @@ void analyze_fn_ir(CodeGen *g, FnTableEntry *fn_table_entry, AstNode *return_typ fn_table_entry->anal_state = FnAnalStateComplete; } -static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) { +void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) { assert(fn_table_entry->anal_state != FnAnalStateProbing); if (fn_table_entry->anal_state != FnAnalStateReady) return; @@ -4022,7 +3991,8 @@ void semantic_analyze(CodeGen *g) { for (; g->resolve_queue_index < g->resolve_queue.length; g->resolve_queue_index += 1) { Tld *tld = g->resolve_queue.at(g->resolve_queue_index); bool pointer_only = false; - resolve_top_level_decl(g, tld, pointer_only, nullptr); + AstNode *source_node = nullptr; + resolve_top_level_decl(g, tld, pointer_only, source_node); } for (; g->fn_defs_index < g->fn_defs.length; g->fn_defs_index += 1) { @@ -4114,7 +4084,7 @@ bool handle_is_ptr(TypeTableEntry *type_entry) { case TypeTableEntryIdInt: case TypeTableEntryIdFloat: case TypeTableEntryIdPointer: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdFn: case TypeTableEntryIdEnum: return false; @@ -4122,7 +4092,7 @@ bool handle_is_ptr(TypeTableEntry *type_entry) { case TypeTableEntryIdStruct: return type_has_bits(type_entry); case TypeTableEntryIdErrorUnion: - return type_has_bits(type_entry->data.error.child_type); + return type_has_bits(type_entry->data.error_union.payload_type); case TypeTableEntryIdMaybe: return type_has_bits(type_entry->data.maybe.child_type) && type_entry->data.maybe.child_type->id != TypeTableEntryIdPointer && @@ -4386,9 +4356,9 @@ static uint32_t hash_const_val(ConstExprValue *const_val) { case TypeTableEntryIdErrorUnion: // TODO better hashing algorithm return 3415065496; - case TypeTableEntryIdPureError: - // TODO better hashing algorithm - return 2630160122; + case TypeTableEntryIdErrorSet: + assert(const_val->data.x_err_set != nullptr); + return const_val->data.x_err_set->value ^ 2630160122; case TypeTableEntryIdFn: return 4133894920 ^ hash_ptr(const_val->data.x_fn.fn_entry); case TypeTableEntryIdNamespace: @@ -4515,7 +4485,7 @@ bool type_requires_comptime(TypeTableEntry *type_entry) { case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: case TypeTableEntryIdEnum: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdFn: case TypeTableEntryIdBool: case TypeTableEntryIdInt: @@ -4894,8 +4864,8 @@ bool const_values_equal(ConstExprValue *a, ConstExprValue *b) { return a->data.x_type == b->data.x_type; case TypeTableEntryIdVoid: return true; - case TypeTableEntryIdPureError: - return a->data.x_pure_err == b->data.x_pure_err; + case TypeTableEntryIdErrorSet: + return a->data.x_err_set->value == b->data.x_err_set->value; case TypeTableEntryIdFn: return a->data.x_fn.fn_entry == b->data.x_fn.fn_entry; case TypeTableEntryIdBool: @@ -5256,9 +5226,9 @@ void render_const_value(CodeGen *g, Buf *buf, ConstExprValue *const_val) { buf_appendf(buf, "(union %s constant)", buf_ptr(&type_entry->name)); return; } - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: { - buf_appendf(buf, "(pure error constant)"); + buf_appendf(buf, "%s.%s", buf_ptr(&type_entry->name), buf_ptr(&const_val->data.x_err_set->name)); return; } case TypeTableEntryIdArgTuple: @@ -5319,8 +5289,7 @@ uint32_t type_id_hash(TypeId x) { case TypeTableEntryIdUndefLit: case TypeTableEntryIdNullLit: case TypeTableEntryIdMaybe: - case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdEnum: case TypeTableEntryIdUnion: case TypeTableEntryIdFn: @@ -5329,6 +5298,8 @@ uint32_t type_id_hash(TypeId x) { case TypeTableEntryIdBoundFn: case TypeTableEntryIdArgTuple: zig_unreachable(); + case TypeTableEntryIdErrorUnion: + return hash_ptr(x.data.error_union.err_set_type) ^ hash_ptr(x.data.error_union.payload_type); case TypeTableEntryIdPointer: return hash_ptr(x.data.pointer.child_type) + (x.data.pointer.is_const ? (uint32_t)2749109194 : (uint32_t)4047371087) + @@ -5363,8 +5334,7 @@ bool type_id_eql(TypeId a, TypeId b) { case TypeTableEntryIdUndefLit: case TypeTableEntryIdNullLit: case TypeTableEntryIdMaybe: - case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdEnum: case TypeTableEntryIdUnion: case TypeTableEntryIdFn: @@ -5374,6 +5344,10 @@ bool type_id_eql(TypeId a, TypeId b) { case TypeTableEntryIdArgTuple: case TypeTableEntryIdOpaque: zig_unreachable(); + case TypeTableEntryIdErrorUnion: + return a.data.error_union.err_set_type == b.data.error_union.err_set_type && + a.data.error_union.payload_type == b.data.error_union.payload_type; + case TypeTableEntryIdPointer: return a.data.pointer.child_type == b.data.pointer.child_type && a.data.pointer.is_const == b.data.pointer.is_const && @@ -5478,7 +5452,7 @@ static const TypeTableEntryId all_type_ids[] = { TypeTableEntryIdNullLit, TypeTableEntryIdMaybe, TypeTableEntryIdErrorUnion, - TypeTableEntryIdPureError, + TypeTableEntryIdErrorSet, TypeTableEntryIdEnum, TypeTableEntryIdUnion, TypeTableEntryIdFn, @@ -5533,7 +5507,7 @@ size_t type_id_index(TypeTableEntryId id) { return 13; case TypeTableEntryIdErrorUnion: return 14; - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: return 15; case TypeTableEntryIdEnum: return 16; @@ -5590,8 +5564,8 @@ const char *type_id_name(TypeTableEntryId id) { return "Nullable"; case TypeTableEntryIdErrorUnion: return "ErrorUnion"; - case TypeTableEntryIdPureError: - return "Error"; + case TypeTableEntryIdErrorSet: + return "ErrorSet"; case TypeTableEntryIdEnum: return "Enum"; case TypeTableEntryIdUnion: @@ -5640,17 +5614,6 @@ LinkLib *add_link_lib(CodeGen *g, Buf *name) { return link_lib; } -void add_link_lib_symbol(CodeGen *g, Buf *lib_name, Buf *symbol_name) { - LinkLib *link_lib = add_link_lib(g, lib_name); - for (size_t i = 0; i < link_lib->symbols.length; i += 1) { - Buf *existing_symbol_name = link_lib->symbols.at(i); - if (buf_eql_buf(existing_symbol_name, symbol_name)) { - return; - } - } - link_lib->symbols.append(symbol_name); -} - uint32_t get_abi_alignment(CodeGen *g, TypeTableEntry *type_entry) { type_ensure_zero_bits_known(g, type_entry); if (type_entry->zero_bits) return 0; @@ -5696,3 +5659,8 @@ ConstExprValue *get_builtin_value(CodeGen *codegen, const char *name) { return var_value; } +bool type_is_global_error_set(TypeTableEntry *err_set_type) { + assert(err_set_type->id == TypeTableEntryIdErrorSet); + assert(err_set_type->data.error_set.infer_fn == nullptr); + return err_set_type->data.error_set.err_count == UINT32_MAX; +} diff --git a/src/analyze.hpp b/src/analyze.hpp index dab6d17d0c..c0c89cf36b 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -30,7 +30,7 @@ TypeTableEntry *get_slice_type(CodeGen *g, TypeTableEntry *ptr_type); TypeTableEntry *get_partial_container_type(CodeGen *g, Scope *scope, ContainerKind kind, AstNode *decl_node, const char *name, ContainerLayout layout); TypeTableEntry *get_smallest_unsigned_int_type(CodeGen *g, uint64_t x); -TypeTableEntry *get_error_type(CodeGen *g, TypeTableEntry *child_type); +TypeTableEntry *get_error_union_type(CodeGen *g, TypeTableEntry *err_set_type, TypeTableEntry *payload_type); TypeTableEntry *get_bound_fn_type(CodeGen *g, FnTableEntry *fn_entry); TypeTableEntry *get_opaque_type(CodeGen *g, Scope *scope, AstNode *source_node, const char *name); TypeTableEntry *get_struct_type(CodeGen *g, const char *type_name, const char *field_names[], @@ -46,8 +46,6 @@ bool type_has_bits(TypeTableEntry *type_entry); ImportTableEntry *add_source_file(CodeGen *g, PackageTableEntry *package, Buf *abs_full_path, Buf *source_code); -// TODO move these over, these used to be static -bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTableEntry *actual_type); VariableTableEntry *find_variable(CodeGen *g, Scope *orig_context, Buf *name); Tld *find_decl(CodeGen *g, Scope *scope, Buf *name); void resolve_top_level_decl(CodeGen *g, Tld *tld, bool pointer_only, AstNode *source_node); @@ -58,6 +56,7 @@ TypeTableEntry *validate_var_type(CodeGen *g, AstNode *source_node, TypeTableEnt TypeTableEntry *container_ref_type(TypeTableEntry *type_entry); bool type_is_complete(TypeTableEntry *type_entry); bool type_is_invalid(TypeTableEntry *type_entry); +bool type_is_global_error_set(TypeTableEntry *err_set_type); bool type_has_zero_bits_known(TypeTableEntry *type_entry); void resolve_container_type(CodeGen *g, TypeTableEntry *type_entry); ScopeDecls *get_container_scope(TypeTableEntry *type_entry); @@ -176,7 +175,6 @@ bool type_is_copyable(CodeGen *g, TypeTableEntry *type_entry); LinkLib *create_link_lib(Buf *name); bool calling_convention_does_first_arg_return(CallingConvention cc); LinkLib *add_link_lib(CodeGen *codegen, Buf *lib); -void add_link_lib_symbol(CodeGen *g, Buf *lib_name, Buf *symbol_name); uint32_t get_abi_alignment(CodeGen *g, TypeTableEntry *type_entry); TypeTableEntry *get_align_amt_type(CodeGen *g); @@ -188,6 +186,8 @@ void add_fn_export(CodeGen *g, FnTableEntry *fn_table_entry, Buf *symbol_name, G ConstExprValue *get_builtin_value(CodeGen *codegen, const char *name); TypeTableEntry *get_ptr_to_stack_trace_type(CodeGen *g); +void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry); +TypeTableEntry *get_auto_err_set_type(CodeGen *g, FnTableEntry *fn_entry); #endif diff --git a/src/ast_render.cpp b/src/ast_render.cpp index 79cbc1b49a..aed4b3e6db 100644 --- a/src/ast_render.cpp +++ b/src/ast_render.cpp @@ -49,11 +49,12 @@ static const char *bin_op_str(BinOpType bin_op) { case BinOpTypeAssignBitAnd: return "&="; case BinOpTypeAssignBitXor: return "^="; case BinOpTypeAssignBitOr: return "|="; - case BinOpTypeAssignBoolAnd: return "&&="; - case BinOpTypeAssignBoolOr: return "||="; + case BinOpTypeAssignMergeErrorSets: return "||="; case BinOpTypeUnwrapMaybe: return "??"; case BinOpTypeArrayCat: return "++"; case BinOpTypeArrayMult: return "**"; + case BinOpTypeErrorUnion: return "!"; + case BinOpTypeMergeErrorSets: return "||"; } zig_unreachable(); } @@ -67,7 +68,6 @@ static const char *prefix_op_str(PrefixOp prefix_op) { case PrefixOpBinNot: return "~"; case PrefixOpDereference: return "*"; case PrefixOpMaybe: return "?"; - case PrefixOpError: return "%"; case PrefixOpUnwrapMaybe: return "??"; } zig_unreachable(); @@ -174,8 +174,6 @@ static const char *node_type_str(NodeType node_type) { return "Defer"; case NodeTypeVariableDeclaration: return "VariableDeclaration"; - case NodeTypeErrorValueDecl: - return "ErrorValueDecl"; case NodeTypeTestDecl: return "TestDecl"; case NodeTypeIntLiteral: @@ -244,6 +242,8 @@ static const char *node_type_str(NodeType node_type) { return "IfErrorExpr"; case NodeTypeTestExpr: return "TestExpr"; + case NodeTypeErrorSetDecl: + return "ErrorSetDecl"; } zig_unreachable(); } @@ -396,7 +396,6 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { if (child->type == NodeTypeUse || child->type == NodeTypeVariableDeclaration || - child->type == NodeTypeErrorValueDecl || child->type == NodeTypeFnProto) { fprintf(ar->f, ";"); @@ -452,6 +451,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { AstNode *return_type_node = node->data.fn_proto.return_type; assert(return_type_node != nullptr); fprintf(ar->f, " "); + if (node->data.fn_proto.auto_err_set) { + fprintf(ar->f, "!"); + } render_node_grouped(ar, return_type_node); break; } @@ -1017,9 +1019,26 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { render_node_ungrouped(ar, node->data.unwrap_err_expr.op2); break; } + case NodeTypeErrorSetDecl: + { + fprintf(ar->f, "error {\n"); + ar->indent += ar->indent_size; + + for (size_t i = 0; i < node->data.err_set_decl.decls.length; i += 1) { + AstNode *field_node = node->data.err_set_decl.decls.at(i); + assert(field_node->type == NodeTypeSymbol); + print_indent(ar); + print_symbol(ar, field_node->data.symbol_expr.symbol); + fprintf(ar->f, ",\n"); + } + + ar->indent -= ar->indent_size; + print_indent(ar); + fprintf(ar->f, "}"); + break; + } case NodeTypeFnDecl: case NodeTypeParamDecl: - case NodeTypeErrorValueDecl: case NodeTypeTestDecl: case NodeTypeStructField: case NodeTypeUse: diff --git a/src/codegen.cpp b/src/codegen.cpp index b1412b2b59..4f100d75ad 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -92,9 +92,6 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out g->want_h_file = (out_type == OutTypeObj || out_type == OutTypeLib); buf_resize(&g->global_asm, 0); - // reserve index 0 to indicate no error - g->error_decls.append(nullptr); - if (root_src_path) { Buf *src_basename = buf_alloc(); Buf *src_dir = buf_alloc(); @@ -256,6 +253,10 @@ LinkLib *codegen_add_link_lib(CodeGen *g, Buf *name) { return add_link_lib(g, name); } +void codegen_add_forbidden_lib(CodeGen *codegen, Buf *lib) { + codegen->forbidden_libs.append(lib); +} + void codegen_add_framework(CodeGen *g, const char *framework) { g->darwin_frameworks.append(buf_create_from_str(framework)); } @@ -282,9 +283,9 @@ void codegen_set_linker_script(CodeGen *g, const char *linker_script) { } -static void render_const_val(CodeGen *g, ConstExprValue *const_val); +static void render_const_val(CodeGen *g, ConstExprValue *const_val, const char *name); static void render_const_val_global(CodeGen *g, ConstExprValue *const_val, const char *name); -static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val); +static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val, const char *name); static void generate_error_name_table(CodeGen *g); static void addLLVMAttr(LLVMValueRef val, LLVMAttributeIndex attr_index, const char *attr_name) { @@ -410,7 +411,7 @@ static uint32_t get_err_ret_trace_arg_index(CodeGen *g, FnTableEntry *fn_table_e } TypeTableEntry *fn_type = fn_table_entry->type_entry; TypeTableEntry *return_type = fn_type->data.fn.fn_type_id.return_type; - if (return_type->id != TypeTableEntryIdErrorUnion && return_type->id != TypeTableEntryIdPureError) { + if (return_type->id != TypeTableEntryIdErrorUnion && return_type->id != TypeTableEntryIdErrorSet) { return UINT32_MAX; } bool first_arg_ret = type_has_bits(return_type) && handle_is_ptr(return_type); @@ -873,7 +874,7 @@ static LLVMValueRef get_panic_msg_ptr_val(CodeGen *g, PanicMsgId msg_id) { ConstExprValue *array_val = create_const_str_lit(g, buf_msg); init_const_slice(g, val, array_val, 0, buf_len(buf_msg), true); - render_const_val(g, val); + render_const_val(g, val, ""); render_const_val_global(g, val, ""); assert(val->global_refs->llvm_global); @@ -1412,7 +1413,7 @@ static LLVMValueRef ir_llvm_value(CodeGen *g, IrInstruction *instruction) { if (!instruction->llvm_value) { assert(instruction->value.special != ConstValSpecialRuntime); assert(instruction->value.type); - render_const_val(g, &instruction->value); + render_const_val(g, &instruction->value, ""); // we might have to do some pointer casting here due to the way union // values are rendered with a type other than the one we expect if (handle_is_ptr(instruction->value.type)) { @@ -1442,7 +1443,7 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutable *executable, IrIns is_err_return = return_instruction->value->value.data.rh_error_union == RuntimeHintErrorUnionError; // TODO: emit a branch to check if the return value is an error } - } else if (return_type->id == TypeTableEntryIdPureError) { + } else if (return_type->id == TypeTableEntryIdErrorSet) { is_err_return = true; } if (is_err_return) { @@ -1789,7 +1790,8 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, assert(op1->value.type == op2->value.type || op_id == IrBinOpBitShiftLeftLossy || op_id == IrBinOpBitShiftLeftExact || op_id == IrBinOpBitShiftRightLossy || - op_id == IrBinOpBitShiftRightExact); + op_id == IrBinOpBitShiftRightExact || + (op1->value.type->id == TypeTableEntryIdErrorSet && op2->value.type->id == TypeTableEntryIdErrorSet)); TypeTableEntry *type_entry = op1->value.type; bool want_runtime_safety = bin_op_instruction->safety_check_on && @@ -1802,6 +1804,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, case IrBinOpArrayCat: case IrBinOpArrayMult: case IrBinOpRemUnspecified: + case IrBinOpMergeErrorSets: zig_unreachable(); case IrBinOpBoolOr: return LLVMBuildOr(g->builder, op1_value, op2_value, ""); @@ -1823,7 +1826,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, } else if (type_entry->id == TypeTableEntryIdEnum) { LLVMIntPredicate pred = cmp_op_to_int_predicate(op_id, false); return LLVMBuildICmp(g->builder, pred, op1_value, op2_value, ""); - } else if (type_entry->id == TypeTableEntryIdPureError || + } else if (type_entry->id == TypeTableEntryIdErrorSet || type_entry->id == TypeTableEntryIdPointer || type_entry->id == TypeTableEntryIdBool) { @@ -1955,6 +1958,54 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, zig_unreachable(); } +static void add_error_range_check(CodeGen *g, TypeTableEntry *err_set_type, TypeTableEntry *int_type, LLVMValueRef target_val) { + assert(err_set_type->id == TypeTableEntryIdErrorSet); + + if (type_is_global_error_set(err_set_type)) { + LLVMValueRef zero = LLVMConstNull(int_type->type_ref); + LLVMValueRef neq_zero_bit = LLVMBuildICmp(g->builder, LLVMIntNE, target_val, zero, ""); + LLVMValueRef ok_bit; + + BigInt biggest_possible_err_val = {0}; + eval_min_max_value_int(g, int_type, &biggest_possible_err_val, true); + + if (bigint_fits_in_bits(&biggest_possible_err_val, 64, false) && + bigint_as_unsigned(&biggest_possible_err_val) < g->errors_by_index.length) + { + ok_bit = neq_zero_bit; + } else { + LLVMValueRef error_value_count = LLVMConstInt(int_type->type_ref, g->errors_by_index.length, false); + LLVMValueRef in_bounds_bit = LLVMBuildICmp(g->builder, LLVMIntULT, target_val, error_value_count, ""); + ok_bit = LLVMBuildAnd(g->builder, neq_zero_bit, in_bounds_bit, ""); + } + + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrOk"); + LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrFail"); + + LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); + + LLVMPositionBuilderAtEnd(g->builder, fail_block); + gen_safety_crash(g, PanicMsgIdInvalidErrorCode); + + LLVMPositionBuilderAtEnd(g->builder, ok_block); + } else { + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrOk"); + LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrFail"); + + uint32_t err_count = err_set_type->data.error_set.err_count; + LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, target_val, fail_block, err_count); + for (uint32_t i = 0; i < err_count; i += 1) { + LLVMValueRef case_value = LLVMConstInt(g->err_tag_type->type_ref, err_set_type->data.error_set.errors[i]->value, false); + LLVMAddCase(switch_instr, case_value, ok_block); + } + + LLVMPositionBuilderAtEnd(g->builder, fail_block); + gen_safety_crash(g, PanicMsgIdInvalidErrorCode); + + LLVMPositionBuilderAtEnd(g->builder, ok_block); + } +} + static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable, IrInstructionCast *cast_instruction) { @@ -2078,6 +2129,11 @@ static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable, assert(wanted_type->id == TypeTableEntryIdInt); assert(actual_type->id == TypeTableEntryIdBool); return LLVMBuildZExt(g->builder, expr_val, wanted_type->type_ref, ""); + case CastOpErrSet: + if (ir_want_runtime_safety(g, &cast_instruction->base)) { + add_error_range_check(g, wanted_type, g->err_tag_type, expr_val); + } + return expr_val; } zig_unreachable(); } @@ -2139,7 +2195,7 @@ static LLVMValueRef ir_render_int_to_enum(CodeGen *g, IrExecutable *executable, static LLVMValueRef ir_render_int_to_err(CodeGen *g, IrExecutable *executable, IrInstructionIntToErr *instruction) { TypeTableEntry *wanted_type = instruction->base.value.type; - assert(wanted_type->id == TypeTableEntryIdPureError); + assert(wanted_type->id == TypeTableEntryIdErrorSet); TypeTableEntry *actual_type = instruction->target->value.type; assert(actual_type->id == TypeTableEntryIdInt); @@ -2148,32 +2204,7 @@ static LLVMValueRef ir_render_int_to_err(CodeGen *g, IrExecutable *executable, I LLVMValueRef target_val = ir_llvm_value(g, instruction->target); if (ir_want_runtime_safety(g, &instruction->base)) { - LLVMValueRef zero = LLVMConstNull(actual_type->type_ref); - LLVMValueRef neq_zero_bit = LLVMBuildICmp(g->builder, LLVMIntNE, target_val, zero, ""); - LLVMValueRef ok_bit; - - BigInt biggest_possible_err_val = {0}; - eval_min_max_value_int(g, actual_type, &biggest_possible_err_val, true); - - if (bigint_fits_in_bits(&biggest_possible_err_val, 64, false) && - bigint_as_unsigned(&biggest_possible_err_val) < g->error_decls.length) - { - ok_bit = neq_zero_bit; - } else { - LLVMValueRef error_value_count = LLVMConstInt(actual_type->type_ref, g->error_decls.length, false); - LLVMValueRef in_bounds_bit = LLVMBuildICmp(g->builder, LLVMIntULT, target_val, error_value_count, ""); - ok_bit = LLVMBuildAnd(g->builder, neq_zero_bit, in_bounds_bit, ""); - } - - LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrOk"); - LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrFail"); - - LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); - - LLVMPositionBuilderAtEnd(g->builder, fail_block); - gen_safety_crash(g, PanicMsgIdInvalidErrorCode); - - LLVMPositionBuilderAtEnd(g->builder, ok_block); + add_error_range_check(g, wanted_type, actual_type, target_val); } return gen_widen_or_shorten(g, false, actual_type, g->err_tag_type, target_val); @@ -2187,15 +2218,18 @@ static LLVMValueRef ir_render_err_to_int(CodeGen *g, IrExecutable *executable, I TypeTableEntry *actual_type = instruction->target->value.type; LLVMValueRef target_val = ir_llvm_value(g, instruction->target); - if (actual_type->id == TypeTableEntryIdPureError) { + if (actual_type->id == TypeTableEntryIdErrorSet) { return gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base), g->err_tag_type, wanted_type, target_val); } else if (actual_type->id == TypeTableEntryIdErrorUnion) { - if (!type_has_bits(actual_type->data.error.child_type)) { + // this should have been a compile time constant + assert(type_has_bits(actual_type->data.error_union.err_set_type)); + + if (!type_has_bits(actual_type->data.error_union.payload_type)) { return gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base), g->err_tag_type, wanted_type, target_val); } else { - zig_panic("TODO"); + zig_panic("TODO err to int when error union payload type not void"); } } else { zig_unreachable(); @@ -2235,7 +2269,6 @@ static LLVMValueRef ir_render_un_op(CodeGen *g, IrExecutable *executable, IrInst switch (op_id) { case IrUnOpInvalid: - case IrUnOpError: case IrUnOpMaybe: case IrUnOpDereference: zig_unreachable(); @@ -2489,7 +2522,7 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr TypeTableEntry *src_return_type = fn_type_id->return_type; bool ret_has_bits = type_has_bits(src_return_type); bool first_arg_ret = ret_has_bits && handle_is_ptr(src_return_type); - bool prefix_arg_err_ret_stack = g->have_err_ret_tracing && (src_return_type->id == TypeTableEntryIdErrorUnion || src_return_type->id == TypeTableEntryIdPureError); + bool prefix_arg_err_ret_stack = g->have_err_ret_tracing && (src_return_type->id == TypeTableEntryIdErrorUnion || src_return_type->id == TypeTableEntryIdErrorSet); size_t actual_param_count = instruction->arg_count + (first_arg_ret ? 1 : 0) + (prefix_arg_err_ret_stack ? 1 : 0); bool is_var_args = fn_type_id->is_var_args; LLVMValueRef *gen_param_values = allocate(actual_param_count); @@ -2907,7 +2940,7 @@ static LLVMValueRef ir_render_ref(CodeGen *g, IrExecutable *executable, IrInstru static LLVMValueRef ir_render_err_name(CodeGen *g, IrExecutable *executable, IrInstructionErrName *instruction) { assert(g->generate_error_name_table); - if (g->error_decls.length == 1) { + if (g->errors_by_index.length == 1) { LLVMBuildUnreachable(g->builder); return nullptr; } @@ -2915,7 +2948,7 @@ static LLVMValueRef ir_render_err_name(CodeGen *g, IrExecutable *executable, IrI LLVMValueRef err_val = ir_llvm_value(g, instruction->value); if (ir_want_runtime_safety(g, &instruction->base)) { LLVMValueRef zero = LLVMConstNull(LLVMTypeOf(err_val)); - LLVMValueRef end_val = LLVMConstInt(LLVMTypeOf(err_val), g->error_decls.length, false); + LLVMValueRef end_val = LLVMConstInt(LLVMTypeOf(err_val), g->errors_by_index.length, false); add_bounds_check(g, err_val, LLVMIntNE, zero, LLVMIntULT, end_val); } @@ -3393,11 +3426,11 @@ static LLVMValueRef ir_render_overflow_op(CodeGen *g, IrExecutable *executable, static LLVMValueRef ir_render_test_err(CodeGen *g, IrExecutable *executable, IrInstructionTestErr *instruction) { TypeTableEntry *err_union_type = instruction->value->value.type; - TypeTableEntry *child_type = err_union_type->data.error.child_type; + TypeTableEntry *payload_type = err_union_type->data.error_union.payload_type; LLVMValueRef err_union_handle = ir_llvm_value(g, instruction->value); LLVMValueRef err_val; - if (type_has_bits(child_type)) { + if (type_has_bits(payload_type)) { LLVMValueRef err_val_ptr = LLVMBuildStructGEP(g->builder, err_union_handle, err_union_err_index, ""); err_val = gen_load_untyped(g, err_val_ptr, 0, false, ""); } else { @@ -3412,11 +3445,11 @@ static LLVMValueRef ir_render_unwrap_err_code(CodeGen *g, IrExecutable *executab TypeTableEntry *ptr_type = instruction->value->value.type; assert(ptr_type->id == TypeTableEntryIdPointer); TypeTableEntry *err_union_type = ptr_type->data.pointer.child_type; - TypeTableEntry *child_type = err_union_type->data.error.child_type; + TypeTableEntry *payload_type = err_union_type->data.error_union.payload_type; LLVMValueRef err_union_ptr = ir_llvm_value(g, instruction->value); LLVMValueRef err_union_handle = get_handle_value(g, err_union_ptr, err_union_type, ptr_type); - if (type_has_bits(child_type)) { + if (type_has_bits(payload_type)) { LLVMValueRef err_val_ptr = LLVMBuildStructGEP(g->builder, err_union_handle, err_union_err_index, ""); return gen_load_untyped(g, err_val_ptr, 0, false, ""); } else { @@ -3428,13 +3461,17 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutable *execu TypeTableEntry *ptr_type = instruction->value->value.type; assert(ptr_type->id == TypeTableEntryIdPointer); TypeTableEntry *err_union_type = ptr_type->data.pointer.child_type; - TypeTableEntry *child_type = err_union_type->data.error.child_type; + TypeTableEntry *payload_type = err_union_type->data.error_union.payload_type; LLVMValueRef err_union_ptr = ir_llvm_value(g, instruction->value); LLVMValueRef err_union_handle = get_handle_value(g, err_union_ptr, err_union_type, ptr_type); - if (ir_want_runtime_safety(g, &instruction->base) && instruction->safety_check_on && g->error_decls.length > 1) { + if (!type_has_bits(err_union_type->data.error_union.err_set_type)) { + return err_union_handle; + } + + if (ir_want_runtime_safety(g, &instruction->base) && instruction->safety_check_on && g->errors_by_index.length > 1) { LLVMValueRef err_val; - if (type_has_bits(child_type)) { + if (type_has_bits(payload_type)) { LLVMValueRef err_val_ptr = LLVMBuildStructGEP(g->builder, err_union_handle, err_union_err_index, ""); err_val = gen_load_untyped(g, err_val_ptr, 0, false, ""); } else { @@ -3452,7 +3489,7 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutable *execu LLVMPositionBuilderAtEnd(g->builder, ok_block); } - if (type_has_bits(child_type)) { + if (type_has_bits(payload_type)) { return LLVMBuildStructGEP(g->builder, err_union_handle, err_union_payload_index, ""); } else { return nullptr; @@ -3493,10 +3530,12 @@ static LLVMValueRef ir_render_err_wrap_code(CodeGen *g, IrExecutable *executable assert(wanted_type->id == TypeTableEntryIdErrorUnion); - TypeTableEntry *child_type = wanted_type->data.error.child_type; + TypeTableEntry *payload_type = wanted_type->data.error_union.payload_type; + TypeTableEntry *err_set_type = wanted_type->data.error_union.err_set_type; + LLVMValueRef err_val = ir_llvm_value(g, instruction->value); - if (!type_has_bits(child_type)) + if (!type_has_bits(payload_type) || !type_has_bits(err_set_type)) return err_val; assert(instruction->tmp_ptr); @@ -3512,11 +3551,16 @@ static LLVMValueRef ir_render_err_wrap_payload(CodeGen *g, IrExecutable *executa assert(wanted_type->id == TypeTableEntryIdErrorUnion); - TypeTableEntry *child_type = wanted_type->data.error.child_type; + TypeTableEntry *payload_type = wanted_type->data.error_union.payload_type; + TypeTableEntry *err_set_type = wanted_type->data.error_union.err_set_type; + + if (!type_has_bits(err_set_type)) { + return ir_llvm_value(g, instruction->value); + } LLVMValueRef ok_err_val = LLVMConstNull(g->err_tag_type->type_ref); - if (!type_has_bits(child_type)) + if (!type_has_bits(payload_type)) return ok_err_val; assert(instruction->tmp_ptr); @@ -3527,7 +3571,7 @@ static LLVMValueRef ir_render_err_wrap_payload(CodeGen *g, IrExecutable *executa gen_store_untyped(g, ok_err_val, err_tag_ptr, 0, false); LLVMValueRef payload_ptr = LLVMBuildStructGEP(g->builder, instruction->tmp_ptr, err_union_payload_index, ""); - gen_assign_raw(g, payload_ptr, get_pointer_to_type(g, child_type, false), payload_val); + gen_assign_raw(g, payload_ptr, get_pointer_to_type(g, payload_type, false), payload_val); return instruction->tmp_ptr; } @@ -3700,6 +3744,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, case IrInstructionIdArgType: case IrInstructionIdTagType: case IrInstructionIdExport: + case IrInstructionIdErrorUnion: zig_unreachable(); case IrInstructionIdReturn: return ir_render_return(g, executable, (IrInstructionReturn *)instruction); @@ -3847,7 +3892,7 @@ static LLVMValueRef gen_const_ptr_union_recursive(CodeGen *g, ConstExprValue *ar static LLVMValueRef gen_parent_ptr(CodeGen *g, ConstExprValue *val, ConstParent *parent) { switch (parent->id) { case ConstParentIdNone: - render_const_val(g, val); + render_const_val(g, val, ""); render_const_val_global(g, val, ""); return val->global_refs->llvm_global; case ConstParentIdStruct: @@ -3933,7 +3978,7 @@ static LLVMValueRef pack_const_int(CodeGen *g, LLVMTypeRef big_int_type_ref, Con case TypeTableEntryIdUndefLit: case TypeTableEntryIdNullLit: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: @@ -3946,17 +3991,17 @@ static LLVMValueRef pack_const_int(CodeGen *g, LLVMTypeRef big_int_type_ref, Con case TypeTableEntryIdEnum: { assert(type_entry->data.enumeration.decl_node->data.container_decl.init_arg_expr != nullptr); - LLVMValueRef int_val = gen_const_val(g, const_val); + LLVMValueRef int_val = gen_const_val(g, const_val, ""); return LLVMConstZExt(int_val, big_int_type_ref); } case TypeTableEntryIdInt: { - LLVMValueRef int_val = gen_const_val(g, const_val); + LLVMValueRef int_val = gen_const_val(g, const_val, ""); return LLVMConstZExt(int_val, big_int_type_ref); } case TypeTableEntryIdFloat: { - LLVMValueRef float_val = gen_const_val(g, const_val); + LLVMValueRef float_val = gen_const_val(g, const_val, ""); LLVMValueRef int_val = LLVMConstFPToUI(float_val, LLVMIntType((unsigned)type_entry->data.floating.bit_count)); return LLVMConstZExt(int_val, big_int_type_ref); @@ -3965,7 +4010,7 @@ static LLVMValueRef pack_const_int(CodeGen *g, LLVMTypeRef big_int_type_ref, Con case TypeTableEntryIdFn: case TypeTableEntryIdMaybe: { - LLVMValueRef ptr_val = gen_const_val(g, const_val); + LLVMValueRef ptr_val = gen_const_val(g, const_val, ""); LLVMValueRef ptr_size_int_val = LLVMConstPtrToInt(ptr_val, g->builtin_types.entry_usize->type_ref); return LLVMConstZExt(ptr_size_int_val, big_int_type_ref); } @@ -4010,7 +4055,7 @@ static bool is_llvm_value_unnamed_type(TypeTableEntry *type_entry, LLVMValueRef return LLVMTypeOf(val) != type_entry->type_ref; } -static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { +static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val, const char *name) { TypeTableEntry *type_entry = const_val->type; assert(!type_entry->zero_bits); @@ -4026,10 +4071,10 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { switch (type_entry->id) { case TypeTableEntryIdInt: return bigint_to_llvm_const(type_entry->type_ref, &const_val->data.x_bigint); - case TypeTableEntryIdPureError: - assert(const_val->data.x_pure_err); - return LLVMConstInt(g->builtin_types.entry_pure_error->type_ref, - const_val->data.x_pure_err->value, false); + case TypeTableEntryIdErrorSet: + assert(const_val->data.x_err_set != nullptr); + return LLVMConstInt(g->builtin_types.entry_global_error_set->type_ref, + const_val->data.x_err_set->value, false); case TypeTableEntryIdFloat: switch (type_entry->data.floating.bit_count) { case 32: @@ -4063,7 +4108,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { child_type->id == TypeTableEntryIdFn) { if (const_val->data.x_maybe) { - return gen_const_val(g, const_val->data.x_maybe); + return gen_const_val(g, const_val->data.x_maybe, ""); } else { return LLVMConstNull(child_type->type_ref); } @@ -4072,7 +4117,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { LLVMValueRef maybe_val; bool make_unnamed_struct; if (const_val->data.x_maybe) { - child_val = gen_const_val(g, const_val->data.x_maybe); + child_val = gen_const_val(g, const_val->data.x_maybe, ""); maybe_val = LLVMConstAllOnes(LLVMInt1Type()); make_unnamed_struct = is_llvm_value_unnamed_type(const_val->type, child_val); @@ -4116,7 +4161,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { if (src_field_index + 1 == src_field_index_end) { ConstExprValue *field_val = &const_val->data.x_struct.fields[src_field_index]; - LLVMValueRef val = gen_const_val(g, field_val); + LLVMValueRef val = gen_const_val(g, field_val, ""); fields[type_struct_field->gen_index] = val; make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(field_val->type, val); } else { @@ -4156,7 +4201,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { continue; } ConstExprValue *field_val = &const_val->data.x_struct.fields[i]; - LLVMValueRef val = gen_const_val(g, field_val); + LLVMValueRef val = gen_const_val(g, field_val, ""); fields[type_struct_field->gen_index] = val; make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(field_val->type, val); } @@ -4180,7 +4225,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { bool make_unnamed_struct = false; for (uint64_t i = 0; i < len; i += 1) { ConstExprValue *elem_value = &const_val->data.x_array.s_none.elements[i]; - LLVMValueRef val = gen_const_val(g, elem_value); + LLVMValueRef val = gen_const_val(g, elem_value, ""); values[i] = val; make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(elem_value->type, val); } @@ -4215,7 +4260,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { } else { uint64_t field_type_bytes = LLVMStoreSizeOfType(g->target_data_ref, payload_value->type->type_ref); uint64_t pad_bytes = type_entry->data.unionation.union_size_bytes - field_type_bytes; - LLVMValueRef correctly_typed_value = gen_const_val(g, payload_value); + LLVMValueRef correctly_typed_value = gen_const_val(g, payload_value, ""); make_unnamed_struct = is_llvm_value_unnamed_type(payload_value->type, correctly_typed_value) || payload_value->type != type_entry->data.unionation.most_aligned_union_member; @@ -4260,7 +4305,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { return fn_llvm_value(g, const_val->data.x_fn.fn_entry); case TypeTableEntryIdPointer: { - render_const_val_global(g, const_val, ""); + render_const_val_global(g, const_val, name); switch (const_val->data.x_ptr.special) { case ConstPtrSpecialInvalid: case ConstPtrSpecialDiscard: @@ -4268,7 +4313,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { case ConstPtrSpecialRef: { ConstExprValue *pointee = const_val->data.x_ptr.data.ref.pointee; - render_const_val(g, pointee); + render_const_val(g, pointee, ""); render_const_val_global(g, pointee, ""); ConstExprValue *other_val = pointee; const_val->global_refs->llvm_value = LLVMConstBitCast(other_val->global_refs->llvm_global, const_val->type->type_ref); @@ -4330,22 +4375,27 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { } case TypeTableEntryIdErrorUnion: { - TypeTableEntry *child_type = type_entry->data.error.child_type; - if (!type_has_bits(child_type)) { + TypeTableEntry *payload_type = type_entry->data.error_union.payload_type; + TypeTableEntry *err_set_type = type_entry->data.error_union.err_set_type; + if (!type_has_bits(payload_type)) { + assert(type_has_bits(err_set_type)); uint64_t value = const_val->data.x_err_union.err ? const_val->data.x_err_union.err->value : 0; return LLVMConstInt(g->err_tag_type->type_ref, value, false); + } else if (!type_has_bits(err_set_type)) { + assert(type_has_bits(payload_type)); + return gen_const_val(g, const_val->data.x_err_union.payload, ""); } else { LLVMValueRef err_tag_value; LLVMValueRef err_payload_value; bool make_unnamed_struct; if (const_val->data.x_err_union.err) { err_tag_value = LLVMConstInt(g->err_tag_type->type_ref, const_val->data.x_err_union.err->value, false); - err_payload_value = LLVMConstNull(child_type->type_ref); + err_payload_value = LLVMConstNull(payload_type->type_ref); make_unnamed_struct = false; } else { err_tag_value = LLVMConstNull(g->err_tag_type->type_ref); ConstExprValue *payload_val = const_val->data.x_err_union.payload; - err_payload_value = gen_const_val(g, payload_val); + err_payload_value = gen_const_val(g, payload_val, ""); make_unnamed_struct = is_llvm_value_unnamed_type(payload_val->type, err_payload_value); } LLVMValueRef fields[] = { @@ -4380,11 +4430,11 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { zig_unreachable(); } -static void render_const_val(CodeGen *g, ConstExprValue *const_val) { +static void render_const_val(CodeGen *g, ConstExprValue *const_val, const char *name) { if (!const_val->global_refs) const_val->global_refs = allocate(1); if (!const_val->global_refs->llvm_value) - const_val->global_refs->llvm_value = gen_const_val(g, const_val); + const_val->global_refs->llvm_value = gen_const_val(g, const_val, name); if (const_val->global_refs->llvm_global) LLVMSetInitializer(const_val->global_refs->llvm_global, const_val->global_refs->llvm_value); @@ -4410,21 +4460,20 @@ static void render_const_val_global(CodeGen *g, ConstExprValue *const_val, const } static void generate_error_name_table(CodeGen *g) { - if (g->err_name_table != nullptr || !g->generate_error_name_table || g->error_decls.length == 1) { + if (g->err_name_table != nullptr || !g->generate_error_name_table || g->errors_by_index.length == 1) { return; } - assert(g->error_decls.length > 0); + assert(g->errors_by_index.length > 0); TypeTableEntry *u8_ptr_type = get_pointer_to_type(g, g->builtin_types.entry_u8, true); TypeTableEntry *str_type = get_slice_type(g, u8_ptr_type); - LLVMValueRef *values = allocate(g->error_decls.length); + LLVMValueRef *values = allocate(g->errors_by_index.length); values[0] = LLVMGetUndef(str_type->type_ref); - for (size_t i = 1; i < g->error_decls.length; i += 1) { - AstNode *error_decl_node = g->error_decls.at(i); - assert(error_decl_node->type == NodeTypeErrorValueDecl); - Buf *name = error_decl_node->data.error_value_decl.name; + for (size_t i = 1; i < g->errors_by_index.length; i += 1) { + ErrorTableEntry *err_entry = g->errors_by_index.at(i); + Buf *name = &err_entry->name; g->largest_err_name_len = max(g->largest_err_name_len, buf_len(name)); @@ -4443,7 +4492,7 @@ static void generate_error_name_table(CodeGen *g) { values[i] = LLVMConstNamedStruct(str_type->type_ref, fields, 2); } - LLVMValueRef err_name_table_init = LLVMConstArray(str_type->type_ref, values, (unsigned)g->error_decls.length); + LLVMValueRef err_name_table_init = LLVMConstArray(str_type->type_ref, values, (unsigned)g->errors_by_index.length); g->err_name_table = LLVMAddGlobal(g->module, LLVMTypeOf(err_name_table_init), buf_ptr(get_mangled_name(g, buf_create_from_str("__zig_err_name_table"), false))); @@ -4575,6 +4624,28 @@ static void do_code_gen(CodeGen *g) { codegen_add_time_event(g, "Code Generation"); + { + // create debug type for error sets + assert(g->err_enumerators.length == g->errors_by_index.length); + uint64_t tag_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, g->err_tag_type->type_ref); + uint64_t tag_debug_align_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, g->err_tag_type->type_ref); + ZigLLVMDIFile *err_set_di_file = nullptr; + ZigLLVMDIType *err_set_di_type = ZigLLVMCreateDebugEnumerationType(g->dbuilder, + ZigLLVMCompileUnitToScope(g->compile_unit), buf_ptr(&g->builtin_types.entry_global_error_set->name), + err_set_di_file, 0, + tag_debug_size_in_bits, + tag_debug_align_in_bits, + g->err_enumerators.items, g->err_enumerators.length, + g->err_tag_type->di_type, ""); + ZigLLVMReplaceTemporary(g->dbuilder, g->builtin_types.entry_global_error_set->di_type, err_set_di_type); + g->builtin_types.entry_global_error_set->di_type = err_set_di_type; + + for (size_t i = 0; i < g->error_di_types.length; i += 1) { + ZigLLVMDIType **di_type_ptr = g->error_di_types.at(i); + *di_type_ptr = err_set_di_type; + } + } + generate_error_name_table(g); generate_enum_name_tables(g); @@ -4592,7 +4663,7 @@ static void do_code_gen(CodeGen *g) { coerced_value.special = ConstValSpecialStatic; coerced_value.type = var_type; coerced_value.data.x_f128 = bigfloat_to_f128(&const_val->data.x_bigfloat); - LLVMValueRef init_val = gen_const_val(g, &coerced_value); + LLVMValueRef init_val = gen_const_val(g, &coerced_value, ""); gen_global_var(g, var, init_val, var_type); continue; } @@ -4626,8 +4697,9 @@ static void do_code_gen(CodeGen *g) { LLVMSetAlignment(global_value, var->align_bytes); } else { bool exported = (var->linkage == VarLinkageExport); - render_const_val(g, var->value); - render_const_val_global(g, var->value, buf_ptr(get_mangled_name(g, &var->name, exported))); + const char *mangled_name = buf_ptr(get_mangled_name(g, &var->name, exported)); + render_const_val(g, var->value, mangled_name); + render_const_val_global(g, var->value, mangled_name); global_value = var->value->global_refs->llvm_global; if (exported) { @@ -5176,16 +5248,24 @@ static void define_builtin_types(CodeGen *g) { } { - TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdPureError); + TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdErrorSet); buf_init_from_str(&entry->name, "error"); + entry->data.error_set.err_count = UINT32_MAX; // TODO allow overriding this type and keep track of max value and emit an // error if there are too many errors declared g->err_tag_type = g->builtin_types.entry_u16; - g->builtin_types.entry_pure_error = entry; + g->builtin_types.entry_global_error_set = entry; entry->type_ref = g->err_tag_type->type_ref; - entry->di_type = g->err_tag_type->di_type; + + entry->di_type = ZigLLVMCreateReplaceableCompositeType(g->dbuilder, + ZigLLVMTag_DW_enumeration_type(), "error", + ZigLLVMCompileUnitToScope(g->compile_unit), nullptr, 0); + + // reserve index 0 to indicate no error + g->err_enumerators.append(ZigLLVMCreateDebugEnumerator(g->dbuilder, "(none)", 0)); + g->errors_by_index.append(nullptr); g->primitive_type_table.put(&entry->name, entry); } @@ -5815,7 +5895,7 @@ static void prepend_c_type_to_decl_list(CodeGen *g, GenH *gen_h, TypeTableEntry case TypeTableEntryIdBoundFn: case TypeTableEntryIdArgTuple: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: zig_unreachable(); case TypeTableEntryIdVoid: case TypeTableEntryIdUnreachable: @@ -5988,7 +6068,7 @@ static void get_c_type(CodeGen *g, GenH *gen_h, TypeTableEntry *type_entry, Buf return; } case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdFn: zig_panic("TODO implement get_c_type for more types"); case TypeTableEntryIdInvalid: @@ -6155,7 +6235,7 @@ static void gen_h_file(CodeGen *g) { case TypeTableEntryIdUndefLit: case TypeTableEntryIdNullLit: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: @@ -6265,3 +6345,4 @@ PackageTableEntry *codegen_create_package(CodeGen *g, const char *root_src_dir, } return pkg; } + diff --git a/src/codegen.hpp b/src/codegen.hpp index b29cadee55..a7a4b748c4 100644 --- a/src/codegen.hpp +++ b/src/codegen.hpp @@ -36,6 +36,7 @@ void codegen_set_kernel32_lib_dir(CodeGen *codegen, Buf *kernel32_lib_dir); void codegen_set_dynamic_linker(CodeGen *g, Buf *dynamic_linker); void codegen_set_windows_subsystem(CodeGen *g, bool mwindows, bool mconsole); void codegen_add_lib_dir(CodeGen *codegen, const char *dir); +void codegen_add_forbidden_lib(CodeGen *codegen, Buf *lib); LinkLib *codegen_add_link_lib(CodeGen *codegen, Buf *lib); void codegen_add_framework(CodeGen *codegen, const char *name); void codegen_add_rpath(CodeGen *codegen, const char *name); diff --git a/src/ir.cpp b/src/ir.cpp index ae8ef00f2f..2bb40c7e15 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -45,6 +45,59 @@ static LVal make_lval_addr(bool is_const, bool is_volatile) { return { true, is_const, is_volatile }; } +enum ConstCastResultId { + ConstCastResultIdOk, + ConstCastResultIdErrSet, + ConstCastResultIdErrSetGlobal, + ConstCastResultIdPointerChild, + ConstCastResultIdSliceChild, + ConstCastResultIdNullableChild, + ConstCastResultIdErrorUnionPayload, + ConstCastResultIdErrorUnionErrorSet, + ConstCastResultIdFnAlign, + ConstCastResultIdFnCC, + ConstCastResultIdFnVarArgs, + ConstCastResultIdFnIsGeneric, + ConstCastResultIdFnReturnType, + ConstCastResultIdFnArgCount, + ConstCastResultIdFnGenericArgCount, + ConstCastResultIdFnArg, + ConstCastResultIdFnArgNoAlias, + ConstCastResultIdType, + ConstCastResultIdUnresolvedInferredErrSet, +}; + +struct ConstCastErrSetMismatch { + ZigList missing_errors; +}; + +struct ConstCastOnly; + +struct ConstCastArg { + size_t arg_index; + ConstCastOnly *child; +}; + +struct ConstCastArgNoAlias { + size_t arg_index; +}; + +struct ConstCastOnly { + ConstCastResultId id; + union { + ConstCastErrSetMismatch error_set; + ConstCastOnly *pointer_child; + ConstCastOnly *slice_child; + ConstCastOnly *nullable_child; + ConstCastOnly *error_union_payload; + ConstCastOnly *error_union_error_set; + ConstCastOnly *return_type; + ConstCastArg fn_arg; + ConstCastArgNoAlias arg_no_alias; + } data; +}; + + static IrInstruction *ir_gen_node(IrBuilder *irb, AstNode *node, Scope *scope); static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, Scope *scope, LVal lval); static TypeTableEntry *ir_analyze_instruction(IrAnalyze *ira, IrInstruction *instruction); @@ -580,6 +633,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionErrorReturnTrace return IrInstructionIdErrorReturnTrace; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionErrorUnion *) { + return IrInstructionIdErrorUnion; +} + template static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) { T *special_instruction = allocate(1); @@ -2326,6 +2383,19 @@ static IrInstruction *ir_build_error_return_trace(IrBuilder *irb, Scope *scope, return &instruction->base; } +static IrInstruction *ir_build_error_union(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *err_set, IrInstruction *payload) +{ + IrInstructionErrorUnion *instruction = ir_build_instruction(irb, scope, source_node); + instruction->err_set = err_set; + instruction->payload = payload; + + ir_ref_instruction(err_set, irb->current_basic_block); + ir_ref_instruction(payload, irb->current_basic_block); + + return &instruction->base; +} + static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_scope, size_t *results) { results[ReturnKindUnconditional] = 0; results[ReturnKindError] = 0; @@ -2800,6 +2870,23 @@ static IrInstruction *ir_gen_maybe_ok_or(IrBuilder *irb, Scope *parent_scope, As return ir_build_phi(irb, parent_scope, node, 2, incoming_blocks, incoming_values); } +static IrInstruction *ir_gen_error_union(IrBuilder *irb, Scope *parent_scope, AstNode *node) { + assert(node->type == NodeTypeBinOpExpr); + + AstNode *op1_node = node->data.bin_op_expr.op1; + AstNode *op2_node = node->data.bin_op_expr.op2; + + IrInstruction *err_set = ir_gen_node(irb, op1_node, parent_scope); + if (err_set == irb->codegen->invalid_instruction) + return irb->codegen->invalid_instruction; + + IrInstruction *payload = ir_gen_node(irb, op2_node, parent_scope); + if (payload == irb->codegen->invalid_instruction) + return irb->codegen->invalid_instruction; + + return ir_build_error_union(irb, parent_scope, node, err_set, payload); +} + static IrInstruction *ir_gen_bin_op(IrBuilder *irb, Scope *scope, AstNode *node) { assert(node->type == NodeTypeBinOpExpr); @@ -2835,10 +2922,8 @@ static IrInstruction *ir_gen_bin_op(IrBuilder *irb, Scope *scope, AstNode *node) return ir_gen_assign_op(irb, scope, node, IrBinOpBinXor); case BinOpTypeAssignBitOr: return ir_gen_assign_op(irb, scope, node, IrBinOpBinOr); - case BinOpTypeAssignBoolAnd: - return ir_gen_assign_op(irb, scope, node, IrBinOpBoolAnd); - case BinOpTypeAssignBoolOr: - return ir_gen_assign_op(irb, scope, node, IrBinOpBoolOr); + case BinOpTypeAssignMergeErrorSets: + return ir_gen_assign_op(irb, scope, node, IrBinOpMergeErrorSets); case BinOpTypeBoolOr: return ir_gen_bool_or(irb, scope, node); case BinOpTypeBoolAnd: @@ -2885,8 +2970,12 @@ static IrInstruction *ir_gen_bin_op(IrBuilder *irb, Scope *scope, AstNode *node) return ir_gen_bin_op_id(irb, scope, node, IrBinOpArrayCat); case BinOpTypeArrayMult: return ir_gen_bin_op_id(irb, scope, node, IrBinOpArrayMult); + case BinOpTypeMergeErrorSets: + return ir_gen_bin_op_id(irb, scope, node, IrBinOpMergeErrorSets); case BinOpTypeUnwrapMaybe: return ir_gen_maybe_ok_or(irb, scope, node); + case BinOpTypeErrorUnion: + return ir_gen_error_union(irb, scope, node); } zig_unreachable(); } @@ -3990,8 +4079,6 @@ static IrInstruction *ir_gen_prefix_op_expr(IrBuilder *irb, Scope *scope, AstNod return ir_gen_prefix_op_id_lval(irb, scope, node, IrUnOpDereference, lval); case PrefixOpMaybe: return ir_lval_wrap(irb, scope, ir_gen_prefix_op_id(irb, scope, node, IrUnOpMaybe), lval); - case PrefixOpError: - return ir_lval_wrap(irb, scope, ir_gen_prefix_op_id(irb, scope, node, IrUnOpError), lval); case PrefixOpUnwrapMaybe: return ir_gen_maybe_assert_ok(irb, scope, node, lval); } @@ -4713,12 +4800,8 @@ static IrInstruction *ir_gen_if_err_expr(IrBuilder *irb, Scope *scope, AstNode * IrBasicBlock *else_block = ir_create_basic_block(irb, scope, "TryElse"); IrBasicBlock *endif_block = ir_create_basic_block(irb, scope, "TryEnd"); - IrInstruction *is_comptime; - if (ir_should_inline(irb->exec, scope)) { - is_comptime = ir_build_const_bool(irb, scope, node, true); - } else { - is_comptime = ir_build_test_comptime(irb, scope, node, is_err); - } + bool force_comptime = ir_should_inline(irb->exec, scope); + IrInstruction *is_comptime = force_comptime ? ir_build_const_bool(irb, scope, node, true) : ir_build_test_comptime(irb, scope, node, is_err); ir_build_cond_br(irb, scope, node, is_err, else_block, ok_block, is_comptime); ir_set_cursor_at_end_and_append_block(irb, ok_block); @@ -4727,8 +4810,9 @@ static IrInstruction *ir_gen_if_err_expr(IrBuilder *irb, Scope *scope, AstNode * if (var_symbol) { IrInstruction *var_type = nullptr; bool is_shadowable = false; + IrInstruction *var_is_comptime = force_comptime ? ir_build_const_bool(irb, scope, node, true) : ir_build_test_comptime(irb, scope, node, err_val); VariableTableEntry *var = ir_create_var(irb, node, scope, - var_symbol, var_is_const, var_is_const, is_shadowable, is_comptime); + var_symbol, var_is_const, var_is_const, is_shadowable, var_is_comptime); IrInstruction *var_ptr_value = ir_build_unwrap_err_payload(irb, scope, node, err_val_ptr, false); IrInstruction *var_value = var_is_ptr ? var_ptr_value : ir_build_load_ptr(irb, scope, node, var_ptr_value); @@ -5165,7 +5249,7 @@ static IrInstruction *ir_gen_continue(IrBuilder *irb, Scope *continue_scope, Ast static IrInstruction *ir_gen_error_type(IrBuilder *irb, Scope *scope, AstNode *node) { assert(node->type == NodeTypeErrorType); - return ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_pure_error); + return ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_global_error_set); } static IrInstruction *ir_gen_defer(IrBuilder *irb, Scope *parent_scope, AstNode *node) { @@ -5249,8 +5333,6 @@ static IrInstruction *ir_gen_err_ok_or(IrBuilder *irb, Scope *parent_scope, AstN Scope *err_scope; if (var_node) { assert(var_node->type == NodeTypeSymbol); - IrInstruction *var_type = ir_build_const_type(irb, parent_scope, node, - irb->codegen->builtin_types.entry_pure_error); Buf *var_name = var_node->data.symbol_expr.symbol; bool is_const = true; bool is_shadowable = false; @@ -5258,7 +5340,7 @@ static IrInstruction *ir_gen_err_ok_or(IrBuilder *irb, Scope *parent_scope, AstN is_const, is_const, is_shadowable, is_comptime); err_scope = var->child_scope; IrInstruction *err_val = ir_build_unwrap_err_code(irb, err_scope, node, err_union_ptr); - ir_build_var_decl(irb, err_scope, var_node, var, var_type, nullptr, err_val); + ir_build_var_decl(irb, err_scope, var_node, var, nullptr, nullptr, err_val); } else { err_scope = parent_scope; } @@ -5348,6 +5430,135 @@ static IrInstruction *ir_gen_container_decl(IrBuilder *irb, Scope *parent_scope, return ir_build_const_type(irb, parent_scope, node, container_type); } +// errors should be populated with set1's values +static TypeTableEntry *get_error_set_union(CodeGen *g, ErrorTableEntry **errors, TypeTableEntry *set1, TypeTableEntry *set2) { + assert(set1->id == TypeTableEntryIdErrorSet); + assert(set2->id == TypeTableEntryIdErrorSet); + + TypeTableEntry *err_set_type = new_type_table_entry(TypeTableEntryIdErrorSet); + buf_resize(&err_set_type->name, 0); + buf_appendf(&err_set_type->name, "error{"); + + for (uint32_t i = 0, count = set1->data.error_set.err_count; i < count; i += 1) { + assert(errors[set1->data.error_set.errors[i]->value] == set1->data.error_set.errors[i]); + } + + uint32_t count = set1->data.error_set.err_count; + 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) { + count += 1; + } + } + + err_set_type->is_copyable = true; + err_set_type->type_ref = g->builtin_types.entry_global_error_set->type_ref; + err_set_type->di_type = g->builtin_types.entry_global_error_set->di_type; + err_set_type->data.error_set.err_count = count; + err_set_type->data.error_set.errors = allocate(count); + + for (uint32_t i = 0; i < set1->data.error_set.err_count; i += 1) { + ErrorTableEntry *error_entry = set1->data.error_set.errors[i]; + buf_appendf(&err_set_type->name, "%s,", buf_ptr(&error_entry->name)); + err_set_type->data.error_set.errors[i] = error_entry; + } + + uint32_t index = set1->data.error_set.err_count; + 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)); + err_set_type->data.error_set.errors[index] = error_entry; + index += 1; + } + } + assert(index == count); + assert(count != 0); + + buf_appendf(&err_set_type->name, "}"); + + g->error_di_types.append(&err_set_type->di_type); + + return err_set_type; + +} + +static TypeTableEntry *make_err_set_with_one_item(CodeGen *g, Scope *parent_scope, AstNode *node, + ErrorTableEntry *err_entry) +{ + TypeTableEntry *err_set_type = new_type_table_entry(TypeTableEntryIdErrorSet); + buf_resize(&err_set_type->name, 0); + buf_appendf(&err_set_type->name, "error{%s}", buf_ptr(&err_entry->name)); + err_set_type->is_copyable = true; + err_set_type->type_ref = g->builtin_types.entry_global_error_set->type_ref; + err_set_type->di_type = g->builtin_types.entry_global_error_set->di_type; + err_set_type->data.error_set.err_count = 1; + err_set_type->data.error_set.errors = allocate(1); + + g->error_di_types.append(&err_set_type->di_type); + + err_set_type->data.error_set.errors[0] = err_entry; + + return err_set_type; +} + +static IrInstruction *ir_gen_err_set_decl(IrBuilder *irb, Scope *parent_scope, AstNode *node) { + assert(node->type == NodeTypeErrorSetDecl); + + uint32_t err_count = node->data.err_set_decl.decls.length; + + Buf *type_name = get_anon_type_name(irb->codegen, irb->exec, "error set", node); + TypeTableEntry *err_set_type = new_type_table_entry(TypeTableEntryIdErrorSet); + buf_init_from_buf(&err_set_type->name, type_name); + err_set_type->is_copyable = true; + err_set_type->data.error_set.err_count = err_count; + + if (err_count == 0) { + err_set_type->zero_bits = true; + err_set_type->di_type = irb->codegen->builtin_types.entry_void->di_type; + } else { + err_set_type->type_ref = irb->codegen->builtin_types.entry_global_error_set->type_ref; + err_set_type->di_type = irb->codegen->builtin_types.entry_global_error_set->di_type; + irb->codegen->error_di_types.append(&err_set_type->di_type); + err_set_type->data.error_set.errors = allocate(err_count); + } + + ErrorTableEntry **errors = allocate(irb->codegen->errors_by_index.length + err_count); + + for (uint32_t i = 0; i < err_count; i += 1) { + AstNode *symbol_node = node->data.err_set_decl.decls.at(i); + assert(symbol_node->type == NodeTypeSymbol); + Buf *err_name = symbol_node->data.symbol_expr.symbol; + ErrorTableEntry *err = allocate(1); + err->decl_node = symbol_node; + buf_init_from_buf(&err->name, err_name); + + auto existing_entry = irb->codegen->error_table.put_unique(err_name, err); + if (existing_entry) { + err->value = existing_entry->value->value; + } else { + size_t error_value_count = irb->codegen->errors_by_index.length; + assert((uint32_t)error_value_count < (((uint32_t)1) << (uint32_t)irb->codegen->err_tag_type->data.integral.bit_count)); + err->value = error_value_count; + irb->codegen->errors_by_index.append(err); + irb->codegen->err_enumerators.append(ZigLLVMCreateDebugEnumerator(irb->codegen->dbuilder, + buf_ptr(err_name), error_value_count)); + } + err_set_type->data.error_set.errors[i] = err; + + 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")); + return irb->codegen->invalid_instruction; + } + errors[err->value] = err; + } + free(errors); + return ir_build_const_type(irb, parent_scope, node, err_set_type); +} + static IrInstruction *ir_gen_fn_proto(IrBuilder *irb, Scope *parent_scope, AstNode *node) { assert(node->type == NodeTypeFnProto); @@ -5401,7 +5612,6 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop case NodeTypeStructField: case NodeTypeFnDef: case NodeTypeFnDecl: - case NodeTypeErrorValueDecl: case NodeTypeTestDecl: zig_unreachable(); case NodeTypeBlock: @@ -5482,6 +5692,8 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop return ir_lval_wrap(irb, scope, ir_gen_container_decl(irb, scope, node), lval); case NodeTypeFnProto: return ir_lval_wrap(irb, scope, ir_gen_fn_proto(irb, scope, node), lval); + case NodeTypeErrorSetDecl: + return ir_lval_wrap(irb, scope, ir_gen_err_set_decl(irb, scope, node), lval); } zig_unreachable(); } @@ -6287,6 +6499,274 @@ static bool slice_is_const(TypeTableEntry *type) { return type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const; } +static bool resolve_inferred_error_set(IrAnalyze *ira, TypeTableEntry *err_set_type, AstNode *source_node) { + assert(err_set_type->id == TypeTableEntryIdErrorSet); + FnTableEntry *infer_fn = err_set_type->data.error_set.infer_fn; + if (infer_fn != nullptr) { + if (infer_fn->anal_state == FnAnalStateInvalid) { + return false; + } else if (infer_fn->anal_state == FnAnalStateReady) { + analyze_fn_body(ira->codegen, infer_fn); + if (err_set_type->data.error_set.infer_fn != nullptr) { + assert(ira->codegen->errors.length != 0); + return false; + } + } else { + ir_add_error_node(ira, source_node, + buf_sprintf("cannot resolve inferred error set '%s': function '%s' not fully analyzed yet", + buf_ptr(&err_set_type->name), buf_ptr(&err_set_type->data.error_set.infer_fn->symbol_name))); + return false; + } + } + return true; +} + +static TypeTableEntry *get_error_set_intersection(IrAnalyze *ira, TypeTableEntry *set1, TypeTableEntry *set2, + AstNode *source_node) +{ + assert(set1->id == TypeTableEntryIdErrorSet); + assert(set2->id == TypeTableEntryIdErrorSet); + + if (!resolve_inferred_error_set(ira, set1, source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + if (!resolve_inferred_error_set(ira, set2, source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + if (type_is_global_error_set(set1)) { + return set2; + } + if (type_is_global_error_set(set2)) { + return set1; + } + ErrorTableEntry **errors = allocate(ira->codegen->errors_by_index.length); + for (uint32_t i = 0; i < set1->data.error_set.err_count; i += 1) { + ErrorTableEntry *error_entry = set1->data.error_set.errors[i]; + assert(errors[error_entry->value] == nullptr); + errors[error_entry->value] = error_entry; + } + ZigList intersection_list = {}; + + TypeTableEntry *err_set_type = new_type_table_entry(TypeTableEntryIdErrorSet); + buf_resize(&err_set_type->name, 0); + buf_appendf(&err_set_type->name, "error{"); + + 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)); + } + } + free(errors); + + err_set_type->is_copyable = true; + err_set_type->type_ref = ira->codegen->builtin_types.entry_global_error_set->type_ref; + err_set_type->di_type = ira->codegen->builtin_types.entry_global_error_set->di_type; + err_set_type->data.error_set.err_count = intersection_list.length; + err_set_type->data.error_set.errors = intersection_list.items; + err_set_type->zero_bits = intersection_list.length == 0; + + buf_appendf(&err_set_type->name, "}"); + + ira->codegen->error_di_types.append(&err_set_type->di_type); + + return err_set_type; +} + + +static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, TypeTableEntry *expected_type, + TypeTableEntry *actual_type, AstNode *source_node) +{ + CodeGen *g = ira->codegen; + ConstCastOnly result = {}; + result.id = ConstCastResultIdOk; + + if (expected_type == actual_type) + return result; + + // pointer const + if (expected_type->id == TypeTableEntryIdPointer && + actual_type->id == TypeTableEntryIdPointer && + (!actual_type->data.pointer.is_const || expected_type->data.pointer.is_const) && + (!actual_type->data.pointer.is_volatile || expected_type->data.pointer.is_volatile) && + actual_type->data.pointer.bit_offset == expected_type->data.pointer.bit_offset && + actual_type->data.pointer.unaligned_bit_count == expected_type->data.pointer.unaligned_bit_count && + actual_type->data.pointer.alignment >= expected_type->data.pointer.alignment) + { + ConstCastOnly child = types_match_const_cast_only(ira, expected_type->data.pointer.child_type, actual_type->data.pointer.child_type, source_node); + if (child.id != ConstCastResultIdOk) { + result.id = ConstCastResultIdPointerChild; + result.data.pointer_child = allocate_nonzero(1); + *result.data.pointer_child = child; + } + return result; + } + + // slice const + if (is_slice(expected_type) && is_slice(actual_type)) { + TypeTableEntry *actual_ptr_type = actual_type->data.structure.fields[slice_ptr_index].type_entry; + TypeTableEntry *expected_ptr_type = expected_type->data.structure.fields[slice_ptr_index].type_entry; + if ((!actual_ptr_type->data.pointer.is_const || expected_ptr_type->data.pointer.is_const) && + (!actual_ptr_type->data.pointer.is_volatile || expected_ptr_type->data.pointer.is_volatile) && + actual_ptr_type->data.pointer.bit_offset == expected_ptr_type->data.pointer.bit_offset && + actual_ptr_type->data.pointer.unaligned_bit_count == expected_ptr_type->data.pointer.unaligned_bit_count && + actual_ptr_type->data.pointer.alignment >= expected_ptr_type->data.pointer.alignment) + { + ConstCastOnly child = types_match_const_cast_only(ira, expected_ptr_type->data.pointer.child_type, + actual_ptr_type->data.pointer.child_type, source_node); + if (child.id != ConstCastResultIdOk) { + result.id = ConstCastResultIdSliceChild; + result.data.slice_child = allocate_nonzero(1); + *result.data.slice_child = child; + } + return result; + } + } + + // maybe + if (expected_type->id == TypeTableEntryIdMaybe && actual_type->id == TypeTableEntryIdMaybe) { + ConstCastOnly child = types_match_const_cast_only(ira, expected_type->data.maybe.child_type, actual_type->data.maybe.child_type, source_node); + if (child.id != ConstCastResultIdOk) { + result.id = ConstCastResultIdNullableChild; + result.data.nullable_child = allocate_nonzero(1); + *result.data.nullable_child = child; + } + return result; + } + + // error union + if (expected_type->id == TypeTableEntryIdErrorUnion && actual_type->id == TypeTableEntryIdErrorUnion) { + ConstCastOnly payload_child = types_match_const_cast_only(ira, expected_type->data.error_union.payload_type, actual_type->data.error_union.payload_type, source_node); + if (payload_child.id != ConstCastResultIdOk) { + result.id = ConstCastResultIdErrorUnionPayload; + result.data.error_union_payload = allocate_nonzero(1); + *result.data.error_union_payload = payload_child; + return result; + } + ConstCastOnly error_set_child = types_match_const_cast_only(ira, expected_type->data.error_union.err_set_type, actual_type->data.error_union.err_set_type, source_node); + if (error_set_child.id != ConstCastResultIdOk) { + result.id = ConstCastResultIdErrorUnionErrorSet; + result.data.error_union_error_set = allocate_nonzero(1); + *result.data.error_union_error_set = error_set_child; + return result; + } + return result; + } + + // error set + if (expected_type->id == TypeTableEntryIdErrorSet && actual_type->id == TypeTableEntryIdErrorSet) { + TypeTableEntry *contained_set = actual_type; + TypeTableEntry *container_set = expected_type; + + // if the container set is inferred, then this will always work. + if (container_set->data.error_set.infer_fn != nullptr) { + return result; + } + // if the container set is the global one, it will always work. + if (type_is_global_error_set(container_set)) { + return result; + } + + if (!resolve_inferred_error_set(ira, contained_set, source_node)) { + result.id = ConstCastResultIdUnresolvedInferredErrSet; + return result; + } + + if (type_is_global_error_set(contained_set)) { + result.id = ConstCastResultIdErrSetGlobal; + return result; + } + + ErrorTableEntry **errors = allocate(g->errors_by_index.length); + for (uint32_t i = 0; i < container_set->data.error_set.err_count; i += 1) { + ErrorTableEntry *error_entry = container_set->data.error_set.errors[i]; + assert(errors[error_entry->value] == nullptr); + errors[error_entry->value] = error_entry; + } + for (uint32_t i = 0; i < contained_set->data.error_set.err_count; i += 1) { + ErrorTableEntry *contained_error_entry = contained_set->data.error_set.errors[i]; + ErrorTableEntry *error_entry = errors[contained_error_entry->value]; + if (error_entry == nullptr) { + if (result.id == ConstCastResultIdOk) { + result.id = ConstCastResultIdErrSet; + } + result.data.error_set.missing_errors.append(contained_error_entry); + } + } + free(errors); + return result; + } + + // fn + if (expected_type->id == TypeTableEntryIdFn && + actual_type->id == TypeTableEntryIdFn) + { + if (expected_type->data.fn.fn_type_id.alignment > actual_type->data.fn.fn_type_id.alignment) { + result.id = ConstCastResultIdFnAlign; + return result; + } + if (expected_type->data.fn.fn_type_id.cc != actual_type->data.fn.fn_type_id.cc) { + result.id = ConstCastResultIdFnCC; + return result; + } + if (expected_type->data.fn.fn_type_id.is_var_args != actual_type->data.fn.fn_type_id.is_var_args) { + result.id = ConstCastResultIdFnVarArgs; + return result; + } + if (expected_type->data.fn.is_generic != actual_type->data.fn.is_generic) { + result.id = ConstCastResultIdFnIsGeneric; + return result; + } + if (!expected_type->data.fn.is_generic && + actual_type->data.fn.fn_type_id.return_type->id != TypeTableEntryIdUnreachable) + { + ConstCastOnly child = types_match_const_cast_only(ira, expected_type->data.fn.fn_type_id.return_type, actual_type->data.fn.fn_type_id.return_type, source_node); + if (child.id != ConstCastResultIdOk) { + result.id = ConstCastResultIdFnReturnType; + result.data.return_type = allocate_nonzero(1); + *result.data.return_type = child; + } + return result; + } + if (expected_type->data.fn.fn_type_id.param_count != actual_type->data.fn.fn_type_id.param_count) { + result.id = ConstCastResultIdFnArgCount; + return result; + } + if (expected_type->data.fn.fn_type_id.next_param_index != actual_type->data.fn.fn_type_id.next_param_index) { + result.id = ConstCastResultIdFnGenericArgCount; + return result; + } + assert(expected_type->data.fn.is_generic || + expected_type->data.fn.fn_type_id.next_param_index == expected_type->data.fn.fn_type_id.param_count); + for (size_t i = 0; i < expected_type->data.fn.fn_type_id.next_param_index; i += 1) { + // note it's reversed for parameters + FnTypeParamInfo *actual_param_info = &actual_type->data.fn.fn_type_id.param_info[i]; + FnTypeParamInfo *expected_param_info = &expected_type->data.fn.fn_type_id.param_info[i]; + + ConstCastOnly arg_child = types_match_const_cast_only(ira, actual_param_info->type, expected_param_info->type, source_node); + if (arg_child.id != ConstCastResultIdOk) { + result.id = ConstCastResultIdFnArg; + result.data.fn_arg.arg_index = i; + result.data.fn_arg.child = allocate_nonzero(1); + *result.data.fn_arg.child = arg_child; + return result; + } + + if (expected_param_info->is_noalias != actual_param_info->is_noalias) { + result.id = ConstCastResultIdFnArgNoAlias; + result.data.arg_no_alias.arg_index = i; + return result; + } + } + return result; + } + + result.id = ConstCastResultIdType; + return result; +} + enum ImplicitCastMatchResult { ImplicitCastMatchResultNo, ImplicitCastMatchResultYes, @@ -6296,10 +6776,46 @@ enum ImplicitCastMatchResult { static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, TypeTableEntry *expected_type, TypeTableEntry *actual_type, IrInstruction *value) { - if (types_match_const_cast_only(expected_type, actual_type)) { + AstNode *source_node = value->source_node; + ConstCastOnly const_cast_result = types_match_const_cast_only(ira, expected_type, actual_type, source_node); + if (const_cast_result.id == ConstCastResultIdOk) { return ImplicitCastMatchResultYes; } + // if we got here with error sets, make an error showing the incompatibilities + ZigList *missing_errors = nullptr; + if (const_cast_result.id == ConstCastResultIdErrSet) { + missing_errors = &const_cast_result.data.error_set.missing_errors; + } + if (const_cast_result.id == ConstCastResultIdErrorUnionErrorSet) { + if (const_cast_result.data.error_union_error_set->id == ConstCastResultIdErrSet) { + missing_errors = &const_cast_result.data.error_union_error_set->data.error_set.missing_errors; + } else if (const_cast_result.data.error_union_error_set->id == ConstCastResultIdErrSetGlobal) { + ErrorMsg *msg = ir_add_error(ira, value, + buf_sprintf("expected '%s', found '%s'", buf_ptr(&expected_type->name), buf_ptr(&actual_type->name))); + add_error_note(ira->codegen, msg, value->source_node, + buf_sprintf("unable to cast global error set into smaller set")); + return ImplicitCastMatchResultReportedError; + } + } else if (const_cast_result.id == ConstCastResultIdErrSetGlobal) { + ErrorMsg *msg = ir_add_error(ira, value, + buf_sprintf("expected '%s', found '%s'", buf_ptr(&expected_type->name), buf_ptr(&actual_type->name))); + add_error_note(ira->codegen, msg, value->source_node, + buf_sprintf("unable to cast global error set into smaller set")); + return ImplicitCastMatchResultReportedError; + } + if (missing_errors != nullptr) { + ErrorMsg *msg = ir_add_error(ira, value, + buf_sprintf("expected '%s', found '%s'", buf_ptr(&expected_type->name), buf_ptr(&actual_type->name))); + for (size_t i = 0; i < missing_errors->length; i += 1) { + ErrorTableEntry *error_entry = missing_errors->at(i); + add_error_note(ira->codegen, msg, error_entry->decl_node, + buf_sprintf("'error.%s' not a member of destination error set", buf_ptr(&error_entry->name))); + } + + return ImplicitCastMatchResultReportedError; + } + // implicit conversion from anything to var if (expected_type->id == TypeTableEntryIdVar) { return ImplicitCastMatchResultYes; @@ -6319,25 +6835,25 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, return ImplicitCastMatchResultYes; } - // implicit T to %T + // implicit T to U!T if (expected_type->id == TypeTableEntryIdErrorUnion && - ir_types_match_with_implicit_cast(ira, expected_type->data.error.child_type, actual_type, value)) + ir_types_match_with_implicit_cast(ira, expected_type->data.error_union.payload_type, actual_type, value)) { return ImplicitCastMatchResultYes; } - // implicit conversion from pure error to error union type + // implicit conversion from error set to error union type if (expected_type->id == TypeTableEntryIdErrorUnion && - actual_type->id == TypeTableEntryIdPureError) + actual_type->id == TypeTableEntryIdErrorSet) { return ImplicitCastMatchResultYes; } - // implicit conversion from T to %?T + // implicit conversion from T to U!?T if (expected_type->id == TypeTableEntryIdErrorUnion && - expected_type->data.error.child_type->id == TypeTableEntryIdMaybe && + expected_type->data.error_union.payload_type->id == TypeTableEntryIdMaybe && ir_types_match_with_implicit_cast(ira, - expected_type->data.error.child_type->data.maybe.child_type, + expected_type->data.error_union.payload_type->data.maybe.child_type, actual_type, value)) { return ImplicitCastMatchResultYes; @@ -6374,7 +6890,7 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, assert(ptr_type->id == TypeTableEntryIdPointer); if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && - types_match_const_cast_only(ptr_type->data.pointer.child_type, actual_type->data.array.child_type)) + types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { return ImplicitCastMatchResultYes; } @@ -6392,7 +6908,7 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, TypeTableEntry *array_type = actual_type->data.pointer.child_type; if ((ptr_type->data.pointer.is_const || array_type->data.array.len == 0) && - types_match_const_cast_only(ptr_type->data.pointer.child_type, array_type->data.array.child_type)) + types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, array_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { return ImplicitCastMatchResultYes; } @@ -6408,7 +6924,7 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, expected_type->data.pointer.child_type->data.structure.fields[slice_ptr_index].type_entry; assert(ptr_type->id == TypeTableEntryIdPointer); if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && - types_match_const_cast_only(ptr_type->data.pointer.child_type, actual_type->data.array.child_type)) + types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { return ImplicitCastMatchResultYes; } @@ -6423,7 +6939,7 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, expected_type->data.maybe.child_type->data.structure.fields[slice_ptr_index].type_entry; assert(ptr_type->id == TypeTableEntryIdPointer); if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && - types_match_const_cast_only(ptr_type->data.pointer.child_type, actual_type->data.array.child_type)) + types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { return ImplicitCastMatchResultYes; } @@ -6503,7 +7019,7 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, // implicitly take a const pointer to something if (!type_requires_comptime(actual_type)) { TypeTableEntry *const_ptr_actual = get_pointer_to_type(ira->codegen, actual_type, true); - if (types_match_const_cast_only(expected_type, const_ptr_actual)) { + if (types_match_const_cast_only(ira, expected_type, const_ptr_actual, source_node).id == ConstCastResultIdOk) { return ImplicitCastMatchResultYes; } } @@ -6511,13 +7027,39 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, return ImplicitCastMatchResultNo; } +static void update_errors_helper(CodeGen *g, ErrorTableEntry ***errors, size_t *errors_count) { + size_t old_errors_count = *errors_count; + *errors_count = g->errors_by_index.length; + *errors = reallocate(*errors, old_errors_count, *errors_count); +} + static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_node, IrInstruction **instructions, size_t instruction_count) { assert(instruction_count >= 1); IrInstruction *prev_inst = instructions[0]; if (type_is_invalid(prev_inst->value.type)) { return ira->codegen->builtin_types.entry_invalid; } - bool any_are_pure_error = (prev_inst->value.type->id == TypeTableEntryIdPureError); + ErrorTableEntry **errors = nullptr; + size_t errors_count = 0; + TypeTableEntry *err_set_type = nullptr; + if (prev_inst->value.type->id == TypeTableEntryIdErrorSet) { + if (type_is_global_error_set(prev_inst->value.type)) { + err_set_type = ira->codegen->builtin_types.entry_global_error_set; + } else { + err_set_type = prev_inst->value.type; + if (!resolve_inferred_error_set(ira, err_set_type, prev_inst->source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + update_errors_helper(ira->codegen, &errors, &errors_count); + + for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *error_entry = err_set_type->data.error_set.errors[i]; + assert(errors[error_entry->value] == nullptr); + errors[error_entry->value] = error_entry; + } + } + } + bool any_are_null = (prev_inst->value.type->id == TypeTableEntryIdNullLit); bool convert_to_const_slice = false; for (size_t i = 1; i < instruction_count; i += 1) { @@ -6538,34 +7080,280 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod continue; } - if (prev_type->id == TypeTableEntryIdPureError) { - prev_inst = cur_inst; - continue; - } - if (prev_type->id == TypeTableEntryIdNullLit) { prev_inst = cur_inst; continue; } - if (cur_type->id == TypeTableEntryIdPureError) { - if (prev_type->id == TypeTableEntryIdArray) { - convert_to_const_slice = true; - } - any_are_pure_error = true; - continue; - } - if (cur_type->id == TypeTableEntryIdNullLit) { any_are_null = true; continue; } - if (types_match_const_cast_only(prev_type, cur_type)) { + if (prev_type->id == TypeTableEntryIdErrorSet) { + assert(err_set_type != nullptr); + if (cur_type->id == TypeTableEntryIdErrorSet) { + if (type_is_global_error_set(err_set_type)) { + continue; + } + if (!resolve_inferred_error_set(ira, cur_type, cur_inst->source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + if (type_is_global_error_set(cur_type)) { + err_set_type = ira->codegen->builtin_types.entry_global_error_set; + prev_inst = cur_inst; + continue; + } + + // number of declared errors might have increased now + update_errors_helper(ira->codegen, &errors, &errors_count); + + // if err_set_type is a superset of cur_type, keep err_set_type. + // if cur_type is a superset of err_set_type, switch err_set_type to cur_type + bool prev_is_superset = true; + for (uint32_t i = 0; i < cur_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *contained_error_entry = cur_type->data.error_set.errors[i]; + ErrorTableEntry *error_entry = errors[contained_error_entry->value]; + if (error_entry == nullptr) { + prev_is_superset = false; + break; + } + } + if (prev_is_superset) { + continue; + } + + // unset everything in errors + for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *error_entry = err_set_type->data.error_set.errors[i]; + errors[error_entry->value] = nullptr; + } + for (uint32_t i = 0, count = ira->codegen->errors_by_index.length; i < count; i += 1) { + assert(errors[i] == nullptr); + } + for (uint32_t i = 0; i < cur_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *error_entry = cur_type->data.error_set.errors[i]; + assert(errors[error_entry->value] == nullptr); + errors[error_entry->value] = error_entry; + } + bool cur_is_superset = true; + for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *contained_error_entry = err_set_type->data.error_set.errors[i]; + ErrorTableEntry *error_entry = errors[contained_error_entry->value]; + if (error_entry == nullptr) { + cur_is_superset = false; + break; + } + } + if (cur_is_superset) { + err_set_type = cur_type; + prev_inst = cur_inst; + assert(errors != nullptr); + continue; + } + + // neither of them are supersets. so we invent a new error set type that is a union of both of them + err_set_type = get_error_set_union(ira->codegen, errors, cur_type, err_set_type); + assert(errors != nullptr); + continue; + } else if (cur_type->id == TypeTableEntryIdErrorUnion) { + if (type_is_global_error_set(err_set_type)) { + prev_inst = cur_inst; + continue; + } + TypeTableEntry *cur_err_set_type = cur_type->data.error_union.err_set_type; + if (!resolve_inferred_error_set(ira, cur_err_set_type, cur_inst->source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + if (type_is_global_error_set(cur_err_set_type)) { + err_set_type = ira->codegen->builtin_types.entry_global_error_set; + prev_inst = cur_inst; + continue; + } + + update_errors_helper(ira->codegen, &errors, &errors_count); + + // test if err_set_type is a subset of cur_type's error set + // unset everything in errors + for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *error_entry = err_set_type->data.error_set.errors[i]; + errors[error_entry->value] = nullptr; + } + for (uint32_t i = 0, count = ira->codegen->errors_by_index.length; i < count; i += 1) { + assert(errors[i] == nullptr); + } + for (uint32_t i = 0; i < cur_err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *error_entry = cur_err_set_type->data.error_set.errors[i]; + assert(errors[error_entry->value] == nullptr); + errors[error_entry->value] = error_entry; + } + bool cur_is_superset = true; + for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *contained_error_entry = err_set_type->data.error_set.errors[i]; + ErrorTableEntry *error_entry = errors[contained_error_entry->value]; + if (error_entry == nullptr) { + cur_is_superset = false; + break; + } + } + if (cur_is_superset) { + err_set_type = cur_err_set_type; + prev_inst = cur_inst; + assert(errors != nullptr); + continue; + } + + // not a subset. invent new error set type, union of both of them + err_set_type = get_error_set_union(ira->codegen, errors, cur_err_set_type, err_set_type); + prev_inst = cur_inst; + assert(errors != nullptr); + continue; + } else { + prev_inst = cur_inst; + continue; + } + } + + if (cur_type->id == TypeTableEntryIdErrorSet) { + if (prev_type->id == TypeTableEntryIdArray) { + convert_to_const_slice = true; + } + if (type_is_global_error_set(cur_type)) { + err_set_type = ira->codegen->builtin_types.entry_global_error_set; + continue; + } + if (err_set_type != nullptr && type_is_global_error_set(err_set_type)) { + continue; + } + if (!resolve_inferred_error_set(ira, cur_type, cur_inst->source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + + update_errors_helper(ira->codegen, &errors, &errors_count); + + if (err_set_type == nullptr) { + if (prev_type->id == TypeTableEntryIdErrorUnion) { + err_set_type = prev_type->data.error_union.err_set_type; + } else { + err_set_type = cur_type; + } + for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *error_entry = err_set_type->data.error_set.errors[i]; + assert(errors[error_entry->value] == nullptr); + errors[error_entry->value] = error_entry; + } + if (err_set_type == cur_type) { + continue; + } + } + // check if the cur type error set is a subset + bool prev_is_superset = true; + for (uint32_t i = 0; i < cur_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *contained_error_entry = cur_type->data.error_set.errors[i]; + ErrorTableEntry *error_entry = errors[contained_error_entry->value]; + if (error_entry == nullptr) { + prev_is_superset = false; + break; + } + } + if (prev_is_superset) { + continue; + } + // not a subset. invent new error set type, union of both of them + err_set_type = get_error_set_union(ira->codegen, errors, err_set_type, cur_type); + assert(errors != nullptr); continue; } - if (types_match_const_cast_only(cur_type, prev_type)) { + if (prev_type->id == TypeTableEntryIdErrorUnion && cur_type->id == TypeTableEntryIdErrorUnion) { + TypeTableEntry *prev_payload_type = prev_type->data.error_union.payload_type; + TypeTableEntry *cur_payload_type = cur_type->data.error_union.payload_type; + + bool const_cast_prev = types_match_const_cast_only(ira, prev_payload_type, cur_payload_type, + source_node).id == ConstCastResultIdOk; + bool const_cast_cur = types_match_const_cast_only(ira, cur_payload_type, prev_payload_type, + source_node).id == ConstCastResultIdOk; + + if (const_cast_prev || const_cast_cur) { + if (const_cast_cur) { + prev_inst = cur_inst; + } + + TypeTableEntry *prev_err_set_type = (err_set_type == nullptr) ? prev_type->data.error_union.err_set_type : err_set_type; + TypeTableEntry *cur_err_set_type = cur_type->data.error_union.err_set_type; + + if (!resolve_inferred_error_set(ira, prev_err_set_type, cur_inst->source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + + if (!resolve_inferred_error_set(ira, cur_err_set_type, cur_inst->source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + + if (type_is_global_error_set(prev_err_set_type) || type_is_global_error_set(cur_err_set_type)) { + err_set_type = ira->codegen->builtin_types.entry_global_error_set; + continue; + } + + update_errors_helper(ira->codegen, &errors, &errors_count); + + if (err_set_type == nullptr) { + err_set_type = prev_err_set_type; + for (uint32_t i = 0; i < prev_err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *error_entry = prev_err_set_type->data.error_set.errors[i]; + assert(errors[error_entry->value] == nullptr); + errors[error_entry->value] = error_entry; + } + } + bool prev_is_superset = true; + for (uint32_t i = 0; i < cur_err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *contained_error_entry = cur_err_set_type->data.error_set.errors[i]; + ErrorTableEntry *error_entry = errors[contained_error_entry->value]; + if (error_entry == nullptr) { + prev_is_superset = false; + break; + } + } + if (prev_is_superset) { + continue; + } + // unset all the errors + for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *error_entry = err_set_type->data.error_set.errors[i]; + errors[error_entry->value] = nullptr; + } + for (uint32_t i = 0, count = ira->codegen->errors_by_index.length; i < count; i += 1) { + assert(errors[i] == nullptr); + } + for (uint32_t i = 0; i < cur_err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *error_entry = cur_err_set_type->data.error_set.errors[i]; + assert(errors[error_entry->value] == nullptr); + errors[error_entry->value] = error_entry; + } + bool cur_is_superset = true; + for (uint32_t i = 0; i < prev_err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *contained_error_entry = prev_err_set_type->data.error_set.errors[i]; + ErrorTableEntry *error_entry = errors[contained_error_entry->value]; + if (error_entry == nullptr) { + cur_is_superset = false; + break; + } + } + if (cur_is_superset) { + err_set_type = cur_err_set_type; + continue; + } + + err_set_type = get_error_set_union(ira->codegen, errors, cur_err_set_type, prev_err_set_type); + continue; + } + } + + if (types_match_const_cast_only(ira, prev_type, cur_type, source_node).id == ConstCastResultIdOk) { + continue; + } + + if (types_match_const_cast_only(ira, cur_type, prev_type, source_node).id == ConstCastResultIdOk) { prev_inst = cur_inst; continue; } @@ -6588,26 +7376,41 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod } if (prev_type->id == TypeTableEntryIdErrorUnion && - types_match_const_cast_only(prev_type->data.error.child_type, cur_type)) + types_match_const_cast_only(ira, prev_type->data.error_union.payload_type, cur_type, source_node).id == ConstCastResultIdOk) { continue; } if (cur_type->id == TypeTableEntryIdErrorUnion && - types_match_const_cast_only(cur_type->data.error.child_type, prev_type)) + types_match_const_cast_only(ira, cur_type->data.error_union.payload_type, prev_type, source_node).id == ConstCastResultIdOk) { + if (err_set_type != nullptr) { + TypeTableEntry *cur_err_set_type = cur_type->data.error_union.err_set_type; + if (!resolve_inferred_error_set(ira, cur_err_set_type, cur_inst->source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + if (type_is_global_error_set(cur_err_set_type) || type_is_global_error_set(err_set_type)) { + err_set_type = ira->codegen->builtin_types.entry_global_error_set; + prev_inst = cur_inst; + continue; + } + + update_errors_helper(ira->codegen, &errors, &errors_count); + + err_set_type = get_error_set_union(ira->codegen, errors, err_set_type, cur_err_set_type); + } prev_inst = cur_inst; continue; } if (prev_type->id == TypeTableEntryIdMaybe && - types_match_const_cast_only(prev_type->data.maybe.child_type, cur_type)) + types_match_const_cast_only(ira, prev_type->data.maybe.child_type, cur_type, source_node).id == ConstCastResultIdOk) { continue; } if (cur_type->id == TypeTableEntryIdMaybe && - types_match_const_cast_only(cur_type->data.maybe.child_type, prev_type)) + types_match_const_cast_only(ira, cur_type->data.maybe.child_type, prev_type, source_node).id == ConstCastResultIdOk) { prev_inst = cur_inst; continue; @@ -6645,7 +7448,7 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod if (cur_type->id == TypeTableEntryIdArray && prev_type->id == TypeTableEntryIdArray && cur_type->data.array.len != prev_type->data.array.len && - types_match_const_cast_only(cur_type->data.array.child_type, prev_type->data.array.child_type)) + types_match_const_cast_only(ira, cur_type->data.array.child_type, prev_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { convert_to_const_slice = true; prev_inst = cur_inst; @@ -6654,7 +7457,7 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod if (cur_type->id == TypeTableEntryIdArray && prev_type->id == TypeTableEntryIdArray && cur_type->data.array.len != prev_type->data.array.len && - types_match_const_cast_only(prev_type->data.array.child_type, cur_type->data.array.child_type)) + types_match_const_cast_only(ira, prev_type->data.array.child_type, cur_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { convert_to_const_slice = true; continue; @@ -6663,8 +7466,8 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod if (cur_type->id == TypeTableEntryIdArray && is_slice(prev_type) && (prev_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const || cur_type->data.array.len == 0) && - types_match_const_cast_only(prev_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.child_type, - cur_type->data.array.child_type)) + types_match_const_cast_only(ira, prev_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.child_type, + cur_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { convert_to_const_slice = false; continue; @@ -6673,8 +7476,8 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod if (prev_type->id == TypeTableEntryIdArray && is_slice(cur_type) && (cur_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const || prev_type->data.array.len == 0) && - types_match_const_cast_only(cur_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.child_type, - prev_type->data.array.child_type)) + types_match_const_cast_only(ira, cur_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.child_type, + prev_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { prev_inst = cur_inst; convert_to_const_slice = false; @@ -6714,30 +7517,37 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod return ira->codegen->builtin_types.entry_invalid; } + + free(errors); + if (convert_to_const_slice) { assert(prev_inst->value.type->id == TypeTableEntryIdArray); TypeTableEntry *ptr_type = get_pointer_to_type(ira->codegen, prev_inst->value.type->data.array.child_type, true); TypeTableEntry *slice_type = get_slice_type(ira->codegen, ptr_type); - if (any_are_pure_error) { - return get_error_type(ira->codegen, slice_type); + if (err_set_type != nullptr) { + return get_error_union_type(ira->codegen, err_set_type, slice_type); } else { return slice_type; } - } else if (any_are_pure_error && prev_inst->value.type->id != TypeTableEntryIdPureError) { - if (prev_inst->value.type->id == TypeTableEntryIdNumLitInt || - prev_inst->value.type->id == TypeTableEntryIdNumLitFloat) - { - ir_add_error_node(ira, source_node, - buf_sprintf("unable to make error union out of number literal")); - return ira->codegen->builtin_types.entry_invalid; - } else if (prev_inst->value.type->id == TypeTableEntryIdNullLit) { - ir_add_error_node(ira, source_node, - buf_sprintf("unable to make error union out of null literal")); - return ira->codegen->builtin_types.entry_invalid; - } else if (prev_inst->value.type->id == TypeTableEntryIdErrorUnion) { - return prev_inst->value.type; + } else if (err_set_type != nullptr) { + if (prev_inst->value.type->id == TypeTableEntryIdErrorSet) { + return err_set_type; } else { - return get_error_type(ira->codegen, prev_inst->value.type); + if (prev_inst->value.type->id == TypeTableEntryIdNumLitInt || + prev_inst->value.type->id == TypeTableEntryIdNumLitFloat) + { + ir_add_error_node(ira, source_node, + buf_sprintf("unable to make error union out of number literal")); + return ira->codegen->builtin_types.entry_invalid; + } else if (prev_inst->value.type->id == TypeTableEntryIdNullLit) { + ir_add_error_node(ira, source_node, + buf_sprintf("unable to make error union out of null literal")); + return ira->codegen->builtin_types.entry_invalid; + } else if (prev_inst->value.type->id == TypeTableEntryIdErrorUnion) { + return get_error_union_type(ira->codegen, err_set_type, prev_inst->value.type->data.error_union.payload_type); + } else { + return get_error_union_type(ira->codegen, err_set_type, prev_inst->value.type); + } } } else if (any_are_null && prev_inst->value.type->id != TypeTableEntryIdNullLit) { if (prev_inst->value.type->id == TypeTableEntryIdNumLitInt || @@ -6783,6 +7593,8 @@ static void eval_const_expr_implicit_cast(CastOp cast_op, switch (cast_op) { case CastOpNoCast: zig_unreachable(); + case CastOpErrSet: + zig_panic("TODO"); case CastOpNoop: { copy_const_val(const_val, other_val, other_val->special == ConstValSpecialStatic); @@ -7213,7 +8025,7 @@ static IrInstruction *ir_analyze_err_wrap_payload(IrAnalyze *ira, IrInstruction assert(wanted_type->id == TypeTableEntryIdErrorUnion); if (instr_is_comptime(value)) { - TypeTableEntry *payload_type = wanted_type->data.error.child_type; + TypeTableEntry *payload_type = wanted_type->data.error_union.payload_type; IrInstruction *casted_payload = ir_implicit_cast(ira, value, payload_type); if (type_is_invalid(casted_payload->value.type)) return ira->codegen->invalid_instruction; @@ -7238,19 +8050,64 @@ static IrInstruction *ir_analyze_err_wrap_payload(IrAnalyze *ira, IrInstruction return result; } -static IrInstruction *ir_analyze_err_wrap_code(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value, TypeTableEntry *wanted_type) { - assert(wanted_type->id == TypeTableEntryIdErrorUnion); +static IrInstruction *ir_analyze_err_set_cast(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value, + TypeTableEntry *wanted_type) +{ + assert(value->value.type->id == TypeTableEntryIdErrorSet); + assert(wanted_type->id == TypeTableEntryIdErrorSet); if (instr_is_comptime(value)) { ConstExprValue *val = ir_resolve_const(ira, value, UndefBad); if (!val) return ira->codegen->invalid_instruction; + if (!resolve_inferred_error_set(ira, wanted_type, source_instr->source_node)) { + return ira->codegen->invalid_instruction; + } + if (!type_is_global_error_set(wanted_type)) { + bool subset = false; + for (uint32_t i = 0, count = wanted_type->data.error_set.err_count; i < count; i += 1) { + if (wanted_type->data.error_set.errors[i]->value == val->data.x_err_set->value) { + subset = true; + break; + } + } + if (!subset) { + ir_add_error(ira, source_instr, + buf_sprintf("error.%s not a member of error set '%s'", + buf_ptr(&val->data.x_err_set->name), buf_ptr(&wanted_type->name))); + return ira->codegen->invalid_instruction; + } + } + + IrInstructionConst *const_instruction = ir_create_instruction(&ira->new_irb, + source_instr->scope, source_instr->source_node); + const_instruction->base.value.type = wanted_type; + const_instruction->base.value.special = ConstValSpecialStatic; + const_instruction->base.value.data.x_err_set = val->data.x_err_set; + return &const_instruction->base; + } + + IrInstruction *result = ir_build_cast(&ira->new_irb, source_instr->scope, source_instr->source_node, wanted_type, value, CastOpErrSet); + result->value.type = wanted_type; + return result; +} + +static IrInstruction *ir_analyze_err_wrap_code(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value, TypeTableEntry *wanted_type) { + assert(wanted_type->id == TypeTableEntryIdErrorUnion); + + IrInstruction *casted_value = ir_implicit_cast(ira, value, wanted_type->data.error_union.err_set_type); + + if (instr_is_comptime(casted_value)) { + ConstExprValue *val = ir_resolve_const(ira, casted_value, UndefBad); + if (!val) + return ira->codegen->invalid_instruction; + IrInstructionConst *const_instruction = ir_create_instruction(&ira->new_irb, source_instr->scope, source_instr->source_node); const_instruction->base.value.type = wanted_type; const_instruction->base.value.special = ConstValSpecialStatic; - const_instruction->base.value.data.x_err_union.err = val->data.x_pure_err; + const_instruction->base.value.data.x_err_union.err = val->data.x_err_set; const_instruction->base.value.data.x_err_union.payload = nullptr; return &const_instruction->base; } @@ -7630,9 +8487,12 @@ static IrInstruction *ir_analyze_number_to_literal(IrAnalyze *ira, IrInstruction return result; } -static IrInstruction *ir_analyze_int_to_err(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *target) { +static IrInstruction *ir_analyze_int_to_err(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *target, + TypeTableEntry *wanted_type) +{ assert(target->value.type->id == TypeTableEntryIdInt); assert(!target->value.type->data.integral.is_signed); + assert(wanted_type->id == TypeTableEntryIdErrorSet); if (instr_is_comptime(target)) { ConstExprValue *val = ir_resolve_const(ira, target, UndefBad); @@ -7640,26 +8500,55 @@ static IrInstruction *ir_analyze_int_to_err(IrAnalyze *ira, IrInstruction *sourc return ira->codegen->invalid_instruction; IrInstruction *result = ir_create_const(&ira->new_irb, source_instr->scope, - source_instr->source_node, ira->codegen->builtin_types.entry_pure_error); + source_instr->source_node, wanted_type); - BigInt err_count; - bigint_init_unsigned(&err_count, ira->codegen->error_decls.length); - if (bigint_cmp_zero(&val->data.x_bigint) == CmpEQ || bigint_cmp(&val->data.x_bigint, &err_count) != CmpLT) { - Buf *val_buf = buf_alloc(); - bigint_append_buf(val_buf, &val->data.x_bigint, 10); - ir_add_error(ira, source_instr, - buf_sprintf("integer value %s represents no error", buf_ptr(val_buf))); + if (!resolve_inferred_error_set(ira, wanted_type, source_instr->source_node)) { return ira->codegen->invalid_instruction; } - size_t index = bigint_as_unsigned(&val->data.x_bigint); - AstNode *error_decl_node = ira->codegen->error_decls.at(index); - result->value.data.x_pure_err = error_decl_node->data.error_value_decl.err; - return result; + if (type_is_global_error_set(wanted_type)) { + BigInt err_count; + bigint_init_unsigned(&err_count, ira->codegen->errors_by_index.length); + + if (bigint_cmp_zero(&val->data.x_bigint) == CmpEQ || bigint_cmp(&val->data.x_bigint, &err_count) != CmpLT) { + Buf *val_buf = buf_alloc(); + bigint_append_buf(val_buf, &val->data.x_bigint, 10); + ir_add_error(ira, source_instr, + buf_sprintf("integer value %s represents no error", buf_ptr(val_buf))); + return ira->codegen->invalid_instruction; + } + + size_t index = bigint_as_unsigned(&val->data.x_bigint); + result->value.data.x_err_set = ira->codegen->errors_by_index.at(index); + return result; + } else { + ErrorTableEntry *err = nullptr; + BigInt err_int; + + for (uint32_t i = 0, count = wanted_type->data.error_set.err_count; i < count; i += 1) { + ErrorTableEntry *this_err = wanted_type->data.error_set.errors[i]; + bigint_init_unsigned(&err_int, this_err->value); + if (bigint_cmp(&val->data.x_bigint, &err_int) == CmpEQ) { + err = this_err; + break; + } + } + + if (err == nullptr) { + Buf *val_buf = buf_alloc(); + bigint_append_buf(val_buf, &val->data.x_bigint, 10); + ir_add_error(ira, source_instr, + buf_sprintf("integer value %s represents no error in '%s'", buf_ptr(val_buf), buf_ptr(&wanted_type->name))); + return ira->codegen->invalid_instruction; + } + + result->value.data.x_err_set = err; + return result; + } } IrInstruction *result = ir_build_int_to_err(&ira->new_irb, source_instr->scope, source_instr->source_node, target); - result->value.type = ira->codegen->builtin_types.entry_pure_error; + result->value.type = wanted_type; return result; } @@ -7681,8 +8570,8 @@ static IrInstruction *ir_analyze_err_to_int(IrAnalyze *ira, IrInstruction *sourc ErrorTableEntry *err; if (err_type->id == TypeTableEntryIdErrorUnion) { err = val->data.x_err_union.err; - } else if (err_type->id == TypeTableEntryIdPureError) { - err = val->data.x_pure_err; + } else if (err_type->id == TypeTableEntryIdErrorSet) { + err = val->data.x_err_set; } else { zig_unreachable(); } @@ -7702,8 +8591,36 @@ static IrInstruction *ir_analyze_err_to_int(IrAnalyze *ira, IrInstruction *sourc return result; } + TypeTableEntry *err_set_type; + if (err_type->id == TypeTableEntryIdErrorUnion) { + err_set_type = err_type->data.error_union.err_set_type; + } else if (err_type->id == TypeTableEntryIdErrorSet) { + err_set_type = err_type; + } else { + zig_unreachable(); + } + if (!type_is_global_error_set(err_set_type)) { + if (!resolve_inferred_error_set(ira, err_set_type, source_instr->source_node)) { + return ira->codegen->invalid_instruction; + } + if (err_set_type->data.error_set.err_count == 0) { + IrInstruction *result = ir_create_const(&ira->new_irb, source_instr->scope, + source_instr->source_node, wanted_type); + result->value.type = wanted_type; + bigint_init_unsigned(&result->value.data.x_bigint, 0); + return result; + } else if (err_set_type->data.error_set.err_count == 1) { + IrInstruction *result = ir_create_const(&ira->new_irb, source_instr->scope, + source_instr->source_node, wanted_type); + result->value.type = wanted_type; + ErrorTableEntry *err = err_set_type->data.error_set.errors[0]; + bigint_init_unsigned(&result->value.data.x_bigint, err->value); + return result; + } + } + BigInt bn; - bigint_init_unsigned(&bn, ira->codegen->error_decls.length); + bigint_init_unsigned(&bn, ira->codegen->errors_by_index.length); if (!bigint_fits_in_bits(&bn, wanted_type->data.integral.bit_count, wanted_type->data.integral.is_signed)) { ir_add_error_node(ira, source_instr->source_node, buf_sprintf("too many error values to fit in '%s'", buf_ptr(&wanted_type->name))); @@ -7719,6 +8636,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst TypeTableEntry *wanted_type, IrInstruction *value) { TypeTableEntry *actual_type = value->value.type; + AstNode *source_node = source_instr->source_node; if (type_is_invalid(wanted_type) || type_is_invalid(actual_type)) { return ira->codegen->invalid_instruction; @@ -7728,7 +8646,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst return value; // explicit match or non-const to const - if (types_match_const_cast_only(wanted_type, actual_type)) { + if (types_match_const_cast_only(ira, wanted_type, actual_type, source_node).id == ConstCastResultIdOk) { return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpNoop, false); } @@ -7748,6 +8666,13 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type); } + // explicit error set cast + if (wanted_type->id == TypeTableEntryIdErrorSet && + actual_type->id == TypeTableEntryIdErrorSet) + { + return ir_analyze_err_set_cast(ira, source_instr, value, wanted_type); + } + // explicit cast from int to float if (wanted_type->id == TypeTableEntryIdFloat && actual_type->id == TypeTableEntryIdInt) @@ -7767,7 +8692,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst TypeTableEntry *ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry; assert(ptr_type->id == TypeTableEntryIdPointer); if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && - types_match_const_cast_only(ptr_type->data.pointer.child_type, actual_type->data.array.child_type)) + types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { return ir_analyze_array_to_slice(ira, source_instr, value, wanted_type); } @@ -7785,7 +8710,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst TypeTableEntry *array_type = actual_type->data.pointer.child_type; if ((ptr_type->data.pointer.is_const || array_type->data.array.len == 0) && - types_match_const_cast_only(ptr_type->data.pointer.child_type, array_type->data.array.child_type)) + types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, array_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { return ir_analyze_array_to_slice(ira, source_instr, value, wanted_type); } @@ -7801,7 +8726,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst wanted_type->data.pointer.child_type->data.structure.fields[slice_ptr_index].type_entry; assert(ptr_type->id == TypeTableEntryIdPointer); if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && - types_match_const_cast_only(ptr_type->data.pointer.child_type, actual_type->data.array.child_type)) + types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.pointer.child_type, value); if (type_is_invalid(cast1->value.type)) @@ -7824,7 +8749,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst wanted_type->data.maybe.child_type->data.structure.fields[slice_ptr_index].type_entry; assert(ptr_type->id == TypeTableEntryIdPointer); if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && - types_match_const_cast_only(ptr_type->data.pointer.child_type, actual_type->data.array.child_type)) + types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.maybe.child_type, value); if (type_is_invalid(cast1->value.type)) @@ -7886,7 +8811,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst // explicit cast from child type of maybe type to maybe type if (wanted_type->id == TypeTableEntryIdMaybe) { - if (types_match_const_cast_only(wanted_type->data.maybe.child_type, actual_type)) { + if (types_match_const_cast_only(ira, wanted_type->data.maybe.child_type, actual_type, source_node).id == ConstCastResultIdOk) { return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type); } else if (actual_type->id == TypeTableEntryIdNumLitInt || actual_type->id == TypeTableEntryIdNumLitFloat) @@ -7908,12 +8833,12 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst // explicit cast from child type of error type to error type if (wanted_type->id == TypeTableEntryIdErrorUnion) { - if (types_match_const_cast_only(wanted_type->data.error.child_type, actual_type)) { + if (types_match_const_cast_only(ira, wanted_type->data.error_union.payload_type, actual_type, source_node).id == ConstCastResultIdOk) { return ir_analyze_err_wrap_payload(ira, source_instr, value, wanted_type); } else if (actual_type->id == TypeTableEntryIdNumLitInt || actual_type->id == TypeTableEntryIdNumLitFloat) { - if (ir_num_lit_fits_in_other_type(ira, value, wanted_type->data.error.child_type, true)) { + if (ir_num_lit_fits_in_other_type(ira, value, wanted_type->data.error_union.payload_type, true)) { return ir_analyze_err_wrap_payload(ira, source_instr, value, wanted_type); } else { return ira->codegen->invalid_instruction; @@ -7923,16 +8848,16 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst // explicit cast from [N]T to %[]const T if (wanted_type->id == TypeTableEntryIdErrorUnion && - is_slice(wanted_type->data.error.child_type) && + is_slice(wanted_type->data.error_union.payload_type) && actual_type->id == TypeTableEntryIdArray) { TypeTableEntry *ptr_type = - wanted_type->data.error.child_type->data.structure.fields[slice_ptr_index].type_entry; + wanted_type->data.error_union.payload_type->data.structure.fields[slice_ptr_index].type_entry; assert(ptr_type->id == TypeTableEntryIdPointer); if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && - types_match_const_cast_only(ptr_type->data.pointer.child_type, actual_type->data.array.child_type)) + types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, source_node).id == ConstCastResultIdOk) { - IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error.child_type, value); + IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error_union.payload_type, value); if (type_is_invalid(cast1->value.type)) return ira->codegen->invalid_instruction; @@ -7944,25 +8869,25 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst } } - // explicit cast from pure error to error union type + // explicit cast from error set to error union type if (wanted_type->id == TypeTableEntryIdErrorUnion && - actual_type->id == TypeTableEntryIdPureError) + actual_type->id == TypeTableEntryIdErrorSet) { return ir_analyze_err_wrap_code(ira, source_instr, value, wanted_type); } // explicit cast from T to %?T if (wanted_type->id == TypeTableEntryIdErrorUnion && - wanted_type->data.error.child_type->id == TypeTableEntryIdMaybe && + wanted_type->data.error_union.payload_type->id == TypeTableEntryIdMaybe && actual_type->id != TypeTableEntryIdMaybe) { - TypeTableEntry *wanted_child_type = wanted_type->data.error.child_type->data.maybe.child_type; - if (types_match_const_cast_only(wanted_child_type, actual_type) || + TypeTableEntry *wanted_child_type = wanted_type->data.error_union.payload_type->data.maybe.child_type; + if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node).id == ConstCastResultIdOk || actual_type->id == TypeTableEntryIdNullLit || actual_type->id == TypeTableEntryIdNumLitInt || actual_type->id == TypeTableEntryIdNumLitFloat) { - IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error.child_type, value); + IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error_union.payload_type, value); if (type_is_invalid(cast1->value.type)) return ira->codegen->invalid_instruction; @@ -8031,21 +8956,19 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst return ir_analyze_number_to_literal(ira, source_instr, value, wanted_type); } - // explicit cast from %void to integer type which can fit it + // explicit cast from T!void to integer type which can fit it bool actual_type_is_void_err = actual_type->id == TypeTableEntryIdErrorUnion && - !type_has_bits(actual_type->data.error.child_type); - bool actual_type_is_pure_err = actual_type->id == TypeTableEntryIdPureError; - if ((actual_type_is_void_err || actual_type_is_pure_err) && - wanted_type->id == TypeTableEntryIdInt) - { + !type_has_bits(actual_type->data.error_union.payload_type); + bool actual_type_is_err_set = actual_type->id == TypeTableEntryIdErrorSet; + if ((actual_type_is_void_err || actual_type_is_err_set) && wanted_type->id == TypeTableEntryIdInt) { return ir_analyze_err_to_int(ira, source_instr, value, wanted_type); } - // explicit cast from integer to pure error - if (wanted_type->id == TypeTableEntryIdPureError && actual_type->id == TypeTableEntryIdInt && + // explicit cast from integer to error set + if (wanted_type->id == TypeTableEntryIdErrorSet && actual_type->id == TypeTableEntryIdInt && !actual_type->data.integral.is_signed) { - return ir_analyze_int_to_err(ira, source_instr, value); + return ir_analyze_int_to_err(ira, source_instr, value, wanted_type); } // explicit cast from integer to enum type with no payload @@ -8109,7 +9032,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst // explicit cast from something to const pointer of it if (!type_requires_comptime(actual_type)) { TypeTableEntry *const_ptr_actual = get_pointer_to_type(ira->codegen, actual_type, true); - if (types_match_const_cast_only(wanted_type, const_ptr_actual)) { + if (types_match_const_cast_only(ira, wanted_type, const_ptr_actual, source_node).id == ConstCastResultIdOk) { return ir_analyze_cast_ref(ira, source_instr, value, wanted_type); } } @@ -8471,6 +9394,7 @@ static bool resolve_cmp_op_id(IrBinOp op_id, Cmp cmp) { static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp *bin_op_instruction) { IrInstruction *op1 = bin_op_instruction->op1->other; IrInstruction *op2 = bin_op_instruction->op2->other; + AstNode *source_node = bin_op_instruction->base.source_node; IrBinOp op_id = bin_op_instruction->op_id; bool is_equality_cmp = (op_id == IrBinOpCmpEq || op_id == IrBinOpCmpNotEq); @@ -8503,7 +9427,7 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp } IrInstruction *is_non_null = ir_build_test_nonnull(&ira->new_irb, bin_op_instruction->base.scope, - bin_op_instruction->base.source_node, maybe_op); + source_node, maybe_op); is_non_null->value.type = ira->codegen->builtin_types.entry_bool; if (op_id == IrBinOpCmpEq) { @@ -8514,8 +9438,88 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp return ira->codegen->builtin_types.entry_bool; } + if (op1->value.type->id == TypeTableEntryIdErrorSet && op2->value.type->id == TypeTableEntryIdErrorSet) { + if (!is_equality_cmp) { + ir_add_error_node(ira, source_node, buf_sprintf("operator not allowed for errors")); + return ira->codegen->builtin_types.entry_invalid; + } + TypeTableEntry *intersect_type = get_error_set_intersection(ira, op1->value.type, op2->value.type, source_node); + if (type_is_invalid(intersect_type)) { + return ira->codegen->builtin_types.entry_invalid; + } + + if (!resolve_inferred_error_set(ira, intersect_type, source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + + // exception if one of the operators has the type of the empty error set, we allow the comparison + // (and make it comptime known) + // this is a function which is evaluated at comptime and returns an inferred error set will have an empty + // error set. + if (op1->value.type->data.error_set.err_count == 0 || op2->value.type->data.error_set.err_count == 0) { + bool are_equal = false; + bool answer; + if (op_id == IrBinOpCmpEq) { + answer = are_equal; + } else if (op_id == IrBinOpCmpNotEq) { + answer = !are_equal; + } else { + zig_unreachable(); + } + ConstExprValue *out_val = ir_build_const_from(ira, &bin_op_instruction->base); + out_val->data.x_bool = answer; + return ira->codegen->builtin_types.entry_bool; + } + + if (!type_is_global_error_set(intersect_type)) { + if (intersect_type->data.error_set.err_count == 0) { + ir_add_error_node(ira, source_node, + buf_sprintf("error sets '%s' and '%s' have no common errors", + buf_ptr(&op1->value.type->name), buf_ptr(&op2->value.type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + if (op1->value.type->data.error_set.err_count == 1 && op2->value.type->data.error_set.err_count == 1) { + bool are_equal = true; + bool answer; + if (op_id == IrBinOpCmpEq) { + answer = are_equal; + } else if (op_id == IrBinOpCmpNotEq) { + answer = !are_equal; + } else { + zig_unreachable(); + } + ConstExprValue *out_val = ir_build_const_from(ira, &bin_op_instruction->base); + out_val->data.x_bool = answer; + return ira->codegen->builtin_types.entry_bool; + } + } + + ConstExprValue *op1_val = &op1->value; + ConstExprValue *op2_val = &op2->value; + if (value_is_comptime(op1_val) && value_is_comptime(op2_val)) { + bool answer; + bool are_equal = op1_val->data.x_err_set->value == op2_val->data.x_err_set->value; + if (op_id == IrBinOpCmpEq) { + answer = are_equal; + } else if (op_id == IrBinOpCmpNotEq) { + answer = !are_equal; + } else { + zig_unreachable(); + } + + ConstExprValue *out_val = ir_build_const_from(ira, &bin_op_instruction->base); + out_val->data.x_bool = answer; + return ira->codegen->builtin_types.entry_bool; + } + + ir_build_bin_op_from(&ira->new_irb, &bin_op_instruction->base, op_id, + op1, op2, bin_op_instruction->safety_check_on); + + return ira->codegen->builtin_types.entry_bool; + } + IrInstruction *instructions[] = {op1, op2}; - TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, bin_op_instruction->base.source_node, instructions, 2); + TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, source_node, instructions, 2); if (type_is_invalid(resolved_type)) return resolved_type; type_ensure_zero_bits_known(ira->codegen, resolved_type); @@ -8523,7 +9527,6 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp return resolved_type; - AstNode *source_node = bin_op_instruction->base.source_node; switch (resolved_type->id) { case TypeTableEntryIdInvalid: zig_unreachable(); // handled above @@ -8538,7 +9541,7 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp case TypeTableEntryIdMetaType: case TypeTableEntryIdVoid: case TypeTableEntryIdPointer: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdFn: case TypeTableEntryIdOpaque: case TypeTableEntryIdNamespace: @@ -8692,6 +9695,7 @@ static int ir_eval_math_op(TypeTableEntry *type_entry, ConstExprValue *op1_val, case IrBinOpArrayCat: case IrBinOpArrayMult: case IrBinOpRemUnspecified: + case IrBinOpMergeErrorSets: zig_unreachable(); case IrBinOpBinOr: assert(is_int); @@ -9264,6 +10268,46 @@ static TypeTableEntry *ir_analyze_array_mult(IrAnalyze *ira, IrInstructionBinOp return get_array_type(ira->codegen, child_type, new_array_len); } +static TypeTableEntry *ir_analyze_merge_error_sets(IrAnalyze *ira, IrInstructionBinOp *instruction) { + TypeTableEntry *op1_type = ir_resolve_type(ira, instruction->op1->other); + if (type_is_invalid(op1_type)) + return ira->codegen->builtin_types.entry_invalid; + + TypeTableEntry *op2_type = ir_resolve_type(ira, instruction->op2->other); + if (type_is_invalid(op2_type)) + return ira->codegen->builtin_types.entry_invalid; + + if (type_is_global_error_set(op1_type) || + type_is_global_error_set(op2_type)) + { + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); + out_val->data.x_type = ira->codegen->builtin_types.entry_global_error_set; + return ira->codegen->builtin_types.entry_type; + } + + if (!resolve_inferred_error_set(ira, op1_type, instruction->op1->other->source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + + if (!resolve_inferred_error_set(ira, op2_type, instruction->op2->other->source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + + ErrorTableEntry **errors = allocate(ira->codegen->errors_by_index.length); + for (uint32_t i = 0, count = op1_type->data.error_set.err_count; i < count; i += 1) { + ErrorTableEntry *error_entry = op1_type->data.error_set.errors[i]; + assert(errors[error_entry->value] == nullptr); + errors[error_entry->value] = error_entry; + } + TypeTableEntry *result_type = get_error_set_union(ira->codegen, errors, op1_type, op2_type); + free(errors); + + + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); + out_val->data.x_type = result_type; + return ira->codegen->builtin_types.entry_type; +} + static TypeTableEntry *ir_analyze_instruction_bin_op(IrAnalyze *ira, IrInstructionBinOp *bin_op_instruction) { IrBinOp op_id = bin_op_instruction->op_id; switch (op_id) { @@ -9305,6 +10349,8 @@ static TypeTableEntry *ir_analyze_instruction_bin_op(IrAnalyze *ira, IrInstructi return ir_analyze_array_cat(ira, bin_op_instruction); case IrBinOpArrayMult: return ir_analyze_array_mult(ira, bin_op_instruction); + case IrBinOpMergeErrorSets: + return ir_analyze_merge_error_sets(ira, bin_op_instruction); } zig_unreachable(); } @@ -9326,7 +10372,7 @@ static VarClassRequired get_var_class_required(TypeTableEntry *type_entry) { case TypeTableEntryIdInt: case TypeTableEntryIdFloat: case TypeTableEntryIdVoid: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdFn: return VarClassRequiredAny; case TypeTableEntryIdNumLitFloat: @@ -9352,7 +10398,7 @@ static VarClassRequired get_var_class_required(TypeTableEntry *type_entry) { case TypeTableEntryIdMaybe: return get_var_class_required(type_entry->data.maybe.child_type); case TypeTableEntryIdErrorUnion: - return get_var_class_required(type_entry->data.error.child_type); + return get_var_class_required(type_entry->data.error_union.payload_type); case TypeTableEntryIdStruct: case TypeTableEntryIdEnum: @@ -9587,7 +10633,7 @@ static TypeTableEntry *ir_analyze_instruction_export(IrAnalyze *ira, IrInstructi case TypeTableEntryIdNullLit: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: @@ -9610,7 +10656,7 @@ static TypeTableEntry *ir_analyze_instruction_export(IrAnalyze *ira, IrInstructi case TypeTableEntryIdNullLit: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: zig_panic("TODO export const value of type %s", buf_ptr(&target->value.type->name)); case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: @@ -9644,6 +10690,31 @@ static TypeTableEntry *ir_analyze_instruction_error_return_trace(IrAnalyze *ira, return nullable_type; } +static TypeTableEntry *ir_analyze_instruction_error_union(IrAnalyze *ira, + IrInstructionErrorUnion *instruction) +{ + TypeTableEntry *err_set_type = ir_resolve_type(ira, instruction->err_set->other); + if (type_is_invalid(err_set_type)) + return ira->codegen->builtin_types.entry_invalid; + + TypeTableEntry *payload_type = ir_resolve_type(ira, instruction->payload->other); + if (type_is_invalid(payload_type)) + return ira->codegen->builtin_types.entry_invalid; + + if (err_set_type->id != TypeTableEntryIdErrorSet) { + ir_add_error(ira, instruction->err_set->other, + buf_sprintf("expected error set type, found type '%s'", + buf_ptr(&err_set_type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + + TypeTableEntry *result_type = get_error_union_type(ira->codegen, err_set_type, payload_type); + + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); + out_val->data.x_type = result_type; + return ira->codegen->builtin_types.entry_type; +} + static bool ir_analyze_fn_call_inline_arg(IrAnalyze *ira, AstNode *fn_proto_node, IrInstruction *arg, Scope **exec_scope, size_t *next_proto_i) { @@ -9926,9 +10997,17 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal } AstNode *return_type_node = fn_proto_node->data.fn_proto.return_type; - TypeTableEntry *return_type = analyze_type_expr(ira->codegen, exec_scope, return_type_node); - if (type_is_invalid(return_type)) + TypeTableEntry *specified_return_type = analyze_type_expr(ira->codegen, exec_scope, return_type_node); + if (type_is_invalid(specified_return_type)) return ira->codegen->builtin_types.entry_invalid; + TypeTableEntry *return_type; + TypeTableEntry *inferred_err_set_type = nullptr; + if (fn_proto_node->data.fn_proto.auto_err_set) { + inferred_err_set_type = get_auto_err_set_type(ira->codegen, fn_entry); + return_type = get_error_union_type(ira->codegen, inferred_err_set_type, specified_return_type); + } else { + return_type = specified_return_type; + } IrInstruction *result; @@ -9942,6 +11021,23 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal ira->new_irb.exec->backward_branch_count, ira->new_irb.exec->backward_branch_quota, fn_entry, nullptr, call_instruction->base.source_node, nullptr, ira->new_irb.exec); + if (inferred_err_set_type != nullptr) { + inferred_err_set_type->data.error_set.infer_fn = nullptr; + if (result->value.type->id == TypeTableEntryIdErrorUnion) { + if (result->value.data.x_err_union.err != nullptr) { + inferred_err_set_type->data.error_set.err_count = 1; + inferred_err_set_type->data.error_set.errors = allocate(1); + inferred_err_set_type->data.error_set.errors[0] = result->value.data.x_err_union.err; + } + TypeTableEntry *fn_inferred_err_set_type = result->value.type->data.error_union.err_set_type; + inferred_err_set_type->data.error_set.err_count = fn_inferred_err_set_type->data.error_set.err_count; + inferred_err_set_type->data.error_set.errors = fn_inferred_err_set_type->data.error_set.errors; + } else if (result->value.type->id == TypeTableEntryIdErrorSet) { + inferred_err_set_type->data.error_set.err_count = result->value.type->data.error_set.err_count; + inferred_err_set_type->data.error_set.errors = result->value.type->data.error_set.errors; + } + } + ira->codegen->memoized_fn_eval_table.put(exec_scope, result); if (type_is_invalid(result->value.type)) @@ -10092,12 +11188,17 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal { AstNode *return_type_node = fn_proto_node->data.fn_proto.return_type; - TypeTableEntry *return_type = analyze_type_expr(ira->codegen, impl_fn->child_scope, return_type_node); - if (type_is_invalid(return_type)) + TypeTableEntry *specified_return_type = analyze_type_expr(ira->codegen, impl_fn->child_scope, return_type_node); + if (type_is_invalid(specified_return_type)) return ira->codegen->builtin_types.entry_invalid; - inst_fn_type_id.return_type = return_type; + if (fn_proto_node->data.fn_proto.auto_err_set) { + TypeTableEntry *inferred_err_set_type = get_auto_err_set_type(ira->codegen, impl_fn); + inst_fn_type_id.return_type = get_error_union_type(ira->codegen, inferred_err_set_type, specified_return_type); + } else { + inst_fn_type_id.return_type = specified_return_type; + } - if (type_requires_comptime(return_type)) { + if (type_requires_comptime(specified_return_type)) { // Throw out our work and call the function as if it were comptime. return ir_analyze_fn_call(ira, call_instruction, fn_entry, fn_type, fn_ref, first_arg_ptr, true, FnInlineAuto); } @@ -10128,7 +11229,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal TypeTableEntry *return_type = impl_fn->type_entry->data.fn.fn_type_id.return_type; ir_add_alloca(ira, new_call_instruction, return_type); - if (return_type->id == TypeTableEntryIdPureError || return_type->id == TypeTableEntryIdErrorUnion) { + if (return_type->id == TypeTableEntryIdErrorSet || return_type->id == TypeTableEntryIdErrorUnion) { parent_fn_entry->calls_errorable_function = true; } @@ -10138,7 +11239,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal FnTableEntry *parent_fn_entry = exec_fn_entry(ira->new_irb.exec); assert(fn_type_id->return_type != nullptr); assert(parent_fn_entry != nullptr); - if (fn_type_id->return_type->id == TypeTableEntryIdPureError || fn_type_id->return_type->id == TypeTableEntryIdErrorUnion) { + if (fn_type_id->return_type->id == TypeTableEntryIdErrorSet || fn_type_id->return_type->id == TypeTableEntryIdErrorUnion) { parent_fn_entry->calls_errorable_function = true; } @@ -10257,58 +11358,6 @@ static TypeTableEntry *ir_analyze_instruction_call(IrAnalyze *ira, IrInstruction } } -static TypeTableEntry *ir_analyze_unary_prefix_op_err(IrAnalyze *ira, IrInstructionUnOp *un_op_instruction) { - assert(un_op_instruction->op_id == IrUnOpError); - IrInstruction *value = un_op_instruction->value->other; - - TypeTableEntry *meta_type = ir_resolve_type(ira, value); - if (type_is_invalid(meta_type)) - return ira->codegen->builtin_types.entry_invalid; - - - switch (meta_type->id) { - case TypeTableEntryIdInvalid: // handled above - zig_unreachable(); - - case TypeTableEntryIdVoid: - case TypeTableEntryIdBool: - case TypeTableEntryIdInt: - case TypeTableEntryIdFloat: - case TypeTableEntryIdPointer: - case TypeTableEntryIdArray: - case TypeTableEntryIdStruct: - case TypeTableEntryIdMaybe: - case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: - case TypeTableEntryIdEnum: - case TypeTableEntryIdUnion: - case TypeTableEntryIdFn: - case TypeTableEntryIdBoundFn: - { - ConstExprValue *out_val = ir_build_const_from(ira, &un_op_instruction->base); - TypeTableEntry *result_type = get_error_type(ira->codegen, meta_type); - out_val->data.x_type = result_type; - return ira->codegen->builtin_types.entry_type; - } - case TypeTableEntryIdMetaType: - case TypeTableEntryIdNumLitFloat: - case TypeTableEntryIdNumLitInt: - case TypeTableEntryIdUndefLit: - case TypeTableEntryIdNullLit: - case TypeTableEntryIdNamespace: - case TypeTableEntryIdBlock: - case TypeTableEntryIdUnreachable: - case TypeTableEntryIdVar: - case TypeTableEntryIdArgTuple: - case TypeTableEntryIdOpaque: - ir_add_error_node(ira, un_op_instruction->base.source_node, - buf_sprintf("unable to wrap type '%s' in error type", buf_ptr(&meta_type->name))); - return ira->codegen->builtin_types.entry_invalid; - } - zig_unreachable(); -} - - static TypeTableEntry *ir_analyze_dereference(IrAnalyze *ira, IrInstructionUnOp *un_op_instruction) { IrInstruction *value = un_op_instruction->value->other; @@ -10364,7 +11413,7 @@ static TypeTableEntry *ir_analyze_maybe(IrAnalyze *ira, IrInstructionUnOp *un_op case TypeTableEntryIdNullLit: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdEnum: case TypeTableEntryIdUnion: case TypeTableEntryIdFn: @@ -10474,8 +11523,6 @@ static TypeTableEntry *ir_analyze_instruction_un_op(IrAnalyze *ira, IrInstructio return ir_analyze_dereference(ira, un_op_instruction); case IrUnOpMaybe: return ir_analyze_maybe(ira, un_op_instruction); - case IrUnOpError: - return ir_analyze_unary_prefix_op_err(ira, un_op_instruction); } zig_unreachable(); } @@ -10633,6 +11680,9 @@ static TypeTableEntry *ir_analyze_instruction_phi(IrAnalyze *ira, IrInstructionP IrInstruction *branch_instruction = predecessor->instruction_list.pop(); ir_set_cursor_at_end(&ira->new_irb, predecessor); IrInstruction *casted_value = ir_implicit_cast(ira, new_value, resolved_type); + if (casted_value == ira->codegen->invalid_instruction) { + return ira->codegen->builtin_types.entry_invalid; + } new_incoming_values.items[i] = casted_value; predecessor->instruction_list.append(branch_instruction); @@ -11048,6 +12098,25 @@ static TypeTableEntry *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field } } +static void add_link_lib_symbol(IrAnalyze *ira, Buf *lib_name, Buf *symbol_name, AstNode *source_node) { + LinkLib *link_lib = add_link_lib(ira->codegen, lib_name); + for (size_t i = 0; i < link_lib->symbols.length; i += 1) { + Buf *existing_symbol_name = link_lib->symbols.at(i); + if (buf_eql_buf(existing_symbol_name, symbol_name)) { + return; + } + } + for (size_t i = 0; i < ira->codegen->forbidden_libs.length; i += 1) { + Buf *forbidden_lib_name = ira->codegen->forbidden_libs.at(i); + if (buf_eql_buf(lib_name, forbidden_lib_name)) { + ir_add_error_node(ira, source_node, + buf_sprintf("linking against forbidden library '%s'", buf_ptr(symbol_name))); + } + } + link_lib->symbols.append(symbol_name); +} + + static TypeTableEntry *ir_analyze_decl_ref(IrAnalyze *ira, IrInstruction *source_instruction, Tld *tld) { bool pointer_only = false; resolve_top_level_decl(ira->codegen, tld, pointer_only, source_instruction->source_node); @@ -11063,7 +12132,7 @@ static TypeTableEntry *ir_analyze_decl_ref(IrAnalyze *ira, IrInstruction *source TldVar *tld_var = (TldVar *)tld; VariableTableEntry *var = tld_var->var; if (tld_var->extern_lib_name != nullptr) { - add_link_lib_symbol(ira->codegen, tld_var->extern_lib_name, &var->name); + add_link_lib_symbol(ira, tld_var->extern_lib_name, &var->name, source_instruction->source_node); } return ir_analyze_var_ptr(ira, source_instruction, var, false, false); @@ -11085,7 +12154,7 @@ static TypeTableEntry *ir_analyze_decl_ref(IrAnalyze *ira, IrInstruction *source const_val->data.x_fn.fn_entry = fn_entry; if (tld_fn->extern_lib_name != nullptr) { - add_link_lib_symbol(ira->codegen, tld_fn->extern_lib_name, &fn_entry->symbol_name); + add_link_lib_symbol(ira, tld_fn->extern_lib_name, &fn_entry->symbol_name, source_instruction->source_node); } bool ptr_is_const = true; @@ -11097,6 +12166,17 @@ static TypeTableEntry *ir_analyze_decl_ref(IrAnalyze *ira, IrInstruction *source zig_unreachable(); } +static ErrorTableEntry *find_err_table_entry(TypeTableEntry *err_set_type, Buf *field_name) { + assert(err_set_type->id == TypeTableEntryIdErrorSet); + for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *err_table_entry = err_set_type->data.error_set.errors[i]; + if (buf_eql_buf(&err_table_entry->name, field_name)) { + return err_table_entry; + } + } + return nullptr; +} + static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstructionFieldPtr *field_ptr_instruction) { IrInstruction *container_ptr = field_ptr_instruction->container_ptr->other; if (type_is_invalid(container_ptr->value.type)) @@ -11238,23 +12318,52 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru buf_sprintf("container '%s' has no member called '%s'", buf_ptr(&child_type->name), buf_ptr(field_name))); return ira->codegen->builtin_types.entry_invalid; - } else if (child_type->id == TypeTableEntryIdPureError) { - auto err_table_entry = ira->codegen->error_table.maybe_get(field_name); - if (err_table_entry) { - ConstExprValue *const_val = create_const_vals(1); - const_val->special = ConstValSpecialStatic; - const_val->type = child_type; - const_val->data.x_pure_err = err_table_entry->value; - - bool ptr_is_const = true; - bool ptr_is_volatile = false; - return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, const_val, - child_type, ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile); + } else if (child_type->id == TypeTableEntryIdErrorSet) { + ErrorTableEntry *err_entry; + TypeTableEntry *err_set_type; + if (type_is_global_error_set(child_type)) { + auto existing_entry = ira->codegen->error_table.maybe_get(field_name); + if (existing_entry) { + err_entry = existing_entry->value; + } else { + err_entry = allocate(1); + err_entry->decl_node = field_ptr_instruction->base.source_node; + buf_init_from_buf(&err_entry->name, field_name); + size_t error_value_count = ira->codegen->errors_by_index.length; + assert((uint32_t)error_value_count < (((uint32_t)1) << (uint32_t)ira->codegen->err_tag_type->data.integral.bit_count)); + err_entry->value = error_value_count; + ira->codegen->errors_by_index.append(err_entry); + ira->codegen->err_enumerators.append(ZigLLVMCreateDebugEnumerator(ira->codegen->dbuilder, + buf_ptr(field_name), error_value_count)); + ira->codegen->error_table.put(field_name, err_entry); + } + if (err_entry->set_with_only_this_in_it == nullptr) { + err_entry->set_with_only_this_in_it = make_err_set_with_one_item(ira->codegen, + field_ptr_instruction->base.scope, field_ptr_instruction->base.source_node, + err_entry); + } + err_set_type = err_entry->set_with_only_this_in_it; + } else { + if (!resolve_inferred_error_set(ira, child_type, field_ptr_instruction->base.source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + err_entry = find_err_table_entry(child_type, field_name); + if (err_entry == nullptr) { + ir_add_error(ira, &field_ptr_instruction->base, + buf_sprintf("no error named '%s' in '%s'", buf_ptr(field_name), buf_ptr(&child_type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + err_set_type = child_type; } + ConstExprValue *const_val = create_const_vals(1); + const_val->special = ConstValSpecialStatic; + const_val->type = err_set_type; + const_val->data.x_err_set = err_entry; - ir_add_error(ira, &field_ptr_instruction->base, - buf_sprintf("use of undeclared error value '%s'", buf_ptr(field_name))); - return ira->codegen->builtin_types.entry_invalid; + bool ptr_is_const = true; + bool ptr_is_volatile = false; + return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, const_val, + err_set_type, ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile); } else if (child_type->id == TypeTableEntryIdInt) { if (buf_eql_str(field_name, "bit_count")) { bool ptr_is_const = true; @@ -11337,11 +12446,18 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru return ira->codegen->builtin_types.entry_invalid; } } else if (child_type->id == TypeTableEntryIdErrorUnion) { - if (buf_eql_str(field_name, "Child")) { + if (buf_eql_str(field_name, "Payload")) { bool ptr_is_const = true; bool ptr_is_volatile = false; return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, - create_const_type(ira->codegen, child_type->data.error.child_type), + create_const_type(ira->codegen, child_type->data.error_union.payload_type), + ira->codegen->builtin_types.entry_type, + ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile); + } else if (buf_eql_str(field_name, "ErrorSet")) { + bool ptr_is_const = true; + bool ptr_is_volatile = false; + return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, + create_const_type(ira->codegen, child_type->data.error_union.err_set_type), ira->codegen->builtin_types.entry_type, ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile); } else { @@ -11528,7 +12644,7 @@ static TypeTableEntry *ir_analyze_instruction_typeof(IrAnalyze *ira, IrInstructi case TypeTableEntryIdStruct: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdEnum: case TypeTableEntryIdUnion: case TypeTableEntryIdFn: @@ -11795,7 +12911,7 @@ static TypeTableEntry *ir_analyze_instruction_slice_type(IrAnalyze *ira, case TypeTableEntryIdNumLitInt: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdEnum: case TypeTableEntryIdUnion: case TypeTableEntryIdFn: @@ -11903,7 +13019,7 @@ static TypeTableEntry *ir_analyze_instruction_array_type(IrAnalyze *ira, case TypeTableEntryIdNumLitInt: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdEnum: case TypeTableEntryIdUnion: case TypeTableEntryIdFn: @@ -11956,7 +13072,7 @@ static TypeTableEntry *ir_analyze_instruction_size_of(IrAnalyze *ira, case TypeTableEntryIdStruct: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdEnum: case TypeTableEntryIdUnion: case TypeTableEntryIdFn: @@ -12291,7 +13407,7 @@ static TypeTableEntry *ir_analyze_instruction_switch_target(IrAnalyze *ira, case TypeTableEntryIdPointer: case TypeTableEntryIdFn: case TypeTableEntryIdNamespace: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: if (pointee_val) { ConstExprValue *out_val = ir_build_const_from(ira, &switch_target_instruction->base); copy_const_val(out_val, pointee_val, true); @@ -12361,8 +13477,6 @@ static TypeTableEntry *ir_analyze_instruction_switch_target(IrAnalyze *ira, return target_type; } case TypeTableEntryIdErrorUnion: - // see https://github.com/andrewrk/zig/issues/632 - zig_panic("TODO switch on error union"); case TypeTableEntryIdUnreachable: case TypeTableEntryIdArray: case TypeTableEntryIdStruct: @@ -12887,7 +14001,7 @@ static TypeTableEntry *ir_analyze_min_max(IrAnalyze *ira, IrInstruction *source_ case TypeTableEntryIdNullLit: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdUnion: case TypeTableEntryIdFn: case TypeTableEntryIdNamespace: @@ -12975,7 +14089,7 @@ static TypeTableEntry *ir_analyze_instruction_err_name(IrAnalyze *ira, IrInstruc TypeTableEntry *u8_ptr_type = get_pointer_to_type(ira->codegen, ira->codegen->builtin_types.entry_u8, true); TypeTableEntry *str_type = get_slice_type(ira->codegen, u8_ptr_type); if (casted_value->value.special == ConstValSpecialStatic) { - ErrorTableEntry *err = casted_value->value.data.x_pure_err; + ErrorTableEntry *err = casted_value->value.data.x_err_set; if (!err->cached_error_name_val) { ConstExprValue *array_val = create_const_str_lit(ira->codegen, &err->name); err->cached_error_name_val = create_const_slice(ira->codegen, array_val, 0, buf_len(&err->name), true); @@ -13956,6 +15070,15 @@ static TypeTableEntry *ir_analyze_instruction_member_count(IrAnalyze *ira, IrIns result = container_type->data.structure.src_field_count; } else if (container_type->id == TypeTableEntryIdUnion) { result = container_type->data.unionation.src_field_count; + } else if (container_type->id == TypeTableEntryIdErrorSet) { + if (!resolve_inferred_error_set(ira, container_type, instruction->base.source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + if (type_is_global_error_set(container_type)) { + ir_add_error(ira, &instruction->base, buf_sprintf("global error set member count not available at comptime")); + return ira->codegen->builtin_types.entry_invalid; + } + result = container_type->data.error_set.err_count; } else { ir_add_error(ira, &instruction->base, buf_sprintf("no value count available for type '%s'", buf_ptr(&container_type->name))); return ira->codegen->builtin_types.entry_invalid; @@ -14120,7 +15243,7 @@ static TypeTableEntry *ir_analyze_instruction_align_of(IrAnalyze *ira, IrInstruc case TypeTableEntryIdStruct: case TypeTableEntryIdMaybe: case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: case TypeTableEntryIdEnum: case TypeTableEntryIdUnion: case TypeTableEntryIdFn: @@ -14251,9 +15374,22 @@ static TypeTableEntry *ir_analyze_instruction_test_err(IrAnalyze *ira, IrInstruc } } + TypeTableEntry *err_set_type = type_entry->data.error_union.err_set_type; + if (!resolve_inferred_error_set(ira, err_set_type, instruction->base.source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + if (!type_is_global_error_set(err_set_type) && + err_set_type->data.error_set.err_count == 0) + { + assert(err_set_type->data.error_set.infer_fn == nullptr); + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); + out_val->data.x_bool = false; + return ira->codegen->builtin_types.entry_bool; + } + ir_build_test_err_from(&ira->new_irb, &instruction->base, value); return ira->codegen->builtin_types.entry_bool; - } else if (type_entry->id == TypeTableEntryIdPureError) { + } else if (type_entry->id == TypeTableEntryIdErrorSet) { ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); out_val->data.x_bool = true; return ira->codegen->builtin_types.entry_bool; @@ -14289,13 +15425,13 @@ static TypeTableEntry *ir_analyze_instruction_unwrap_err_code(IrAnalyze *ira, assert(err); ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); - out_val->data.x_pure_err = err; - return ira->codegen->builtin_types.entry_pure_error; + out_val->data.x_err_set = err; + return type_entry->data.error_union.err_set_type; } } ir_build_unwrap_err_code_from(&ira->new_irb, &instruction->base, value); - return ira->codegen->builtin_types.entry_pure_error; + return type_entry->data.error_union.err_set_type; } else { ir_add_error(ira, value, buf_sprintf("expected error union type, found '%s'", buf_ptr(&type_entry->name))); @@ -14319,10 +15455,10 @@ static TypeTableEntry *ir_analyze_instruction_unwrap_err_payload(IrAnalyze *ira, if (type_is_invalid(type_entry)) { return ira->codegen->builtin_types.entry_invalid; } else if (type_entry->id == TypeTableEntryIdErrorUnion) { - TypeTableEntry *child_type = type_entry->data.error.child_type; - TypeTableEntry *result_type = get_pointer_to_type_extra(ira->codegen, child_type, + TypeTableEntry *payload_type = type_entry->data.error_union.payload_type; + TypeTableEntry *result_type = get_pointer_to_type_extra(ira->codegen, payload_type, ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile, - get_abi_alignment(ira->codegen, child_type), 0, 0); + get_abi_alignment(ira->codegen, payload_type), 0, 0); if (instr_is_comptime(value)) { ConstExprValue *ptr_val = ir_resolve_const(ira, value, UndefBad); if (!ptr_val) @@ -14332,7 +15468,7 @@ static TypeTableEntry *ir_analyze_instruction_unwrap_err_payload(IrAnalyze *ira, ErrorTableEntry *err = err_union_val->data.x_err_union.err; if (err != nullptr) { ir_add_error(ira, &instruction->base, - buf_sprintf("unable to unwrap error '%s'", buf_ptr(&err->name))); + buf_sprintf("caught unexpected error '%s'", buf_ptr(&err->name))); return ira->codegen->builtin_types.entry_invalid; } @@ -14357,6 +15493,12 @@ static TypeTableEntry *ir_analyze_instruction_fn_proto(IrAnalyze *ira, IrInstruc AstNode *proto_node = instruction->base.source_node; assert(proto_node->type == NodeTypeFnProto); + if (proto_node->data.fn_proto.auto_err_set) { + ir_add_error(ira, &instruction->base, + buf_sprintf("inferring error set of return type valid only for function definitions")); + return ira->codegen->builtin_types.entry_invalid; + } + FnTypeId fn_type_id = {0}; init_fn_type_id(&fn_type_id, proto_node, proto_node->data.fn_proto.params.length); @@ -14482,6 +15624,63 @@ static TypeTableEntry *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira } } } + } else if (switch_type->id == TypeTableEntryIdErrorSet) { + if (!resolve_inferred_error_set(ira, switch_type, target_value->source_node)) { + return ira->codegen->builtin_types.entry_invalid; + } + + AstNode **field_prev_uses = allocate(ira->codegen->errors_by_index.length); + + for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) { + IrInstructionCheckSwitchProngsRange *range = &instruction->ranges[range_i]; + + IrInstruction *start_value = range->start->other; + if (type_is_invalid(start_value->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + IrInstruction *end_value = range->end->other; + if (type_is_invalid(end_value->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + assert(start_value->value.type->id == TypeTableEntryIdErrorSet); + uint32_t start_index = start_value->value.data.x_err_set->value; + + assert(end_value->value.type->id == TypeTableEntryIdErrorSet); + uint32_t end_index = end_value->value.data.x_err_set->value; + + if (start_index != end_index) { + ir_add_error(ira, end_value, buf_sprintf("ranges not allowed when switching on errors")); + return ira->codegen->builtin_types.entry_invalid; + } + + AstNode *prev_node = field_prev_uses[start_index]; + if (prev_node != nullptr) { + Buf *err_name = &ira->codegen->errors_by_index.at(start_index)->name; + ErrorMsg *msg = ir_add_error(ira, start_value, + buf_sprintf("duplicate switch value: '%s.%s'", buf_ptr(&switch_type->name), buf_ptr(err_name))); + add_error_note(ira->codegen, msg, prev_node, buf_sprintf("other value is here")); + } + field_prev_uses[start_index] = start_value->source_node; + } + if (!instruction->have_else_prong) { + if (type_is_global_error_set(switch_type)) { + ir_add_error(ira, &instruction->base, + buf_sprintf("else prong required when switching on type 'error'")); + return ira->codegen->builtin_types.entry_invalid; + } else { + for (uint32_t i = 0; i < switch_type->data.error_set.err_count; i += 1) { + ErrorTableEntry *err_entry = switch_type->data.error_set.errors[i]; + + AstNode *prev_node = field_prev_uses[err_entry->value]; + if (prev_node == nullptr) { + ir_add_error(ira, &instruction->base, + buf_sprintf("error.%s not handled in switch", buf_ptr(&err_entry->name))); + } + } + } + } + + free(field_prev_uses); } else if (switch_type->id == TypeTableEntryIdInt) { RangeSet rs = {0}; for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) { @@ -14774,7 +15973,7 @@ static void buf_write_value_bytes(CodeGen *codegen, uint8_t *buf, ConstExprValue zig_panic("TODO buf_write_value_bytes maybe type"); case TypeTableEntryIdErrorUnion: zig_panic("TODO buf_write_value_bytes error union"); - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: zig_panic("TODO buf_write_value_bytes pure error type"); case TypeTableEntryIdEnum: zig_panic("TODO buf_write_value_bytes enum type"); @@ -14832,7 +16031,7 @@ static void buf_read_value_bytes(CodeGen *codegen, uint8_t *buf, ConstExprValue zig_panic("TODO buf_read_value_bytes maybe type"); case TypeTableEntryIdErrorUnion: zig_panic("TODO buf_read_value_bytes error union"); - case TypeTableEntryIdPureError: + case TypeTableEntryIdErrorSet: zig_panic("TODO buf_read_value_bytes pure error type"); case TypeTableEntryIdEnum: zig_panic("TODO buf_read_value_bytes enum type"); @@ -15010,7 +16209,7 @@ static TypeTableEntry *ir_analyze_instruction_decl_ref(IrAnalyze *ira, return ira->codegen->builtin_types.entry_invalid; if (tld_var->extern_lib_name != nullptr) { - add_link_lib_symbol(ira->codegen, tld_var->extern_lib_name, &var->name); + add_link_lib_symbol(ira, tld_var->extern_lib_name, &var->name, instruction->base.source_node); } if (lval.is_ptr) { @@ -15029,7 +16228,7 @@ static TypeTableEntry *ir_analyze_instruction_decl_ref(IrAnalyze *ira, assert(fn_entry->type_entry); if (tld_fn->extern_lib_name != nullptr) { - add_link_lib_symbol(ira->codegen, tld_fn->extern_lib_name, &fn_entry->symbol_name); + add_link_lib_symbol(ira, tld_fn->extern_lib_name, &fn_entry->symbol_name, instruction->base.source_node); } IrInstruction *ref_instruction = ir_create_const_fn(&ira->new_irb, instruction->base.scope, @@ -15443,6 +16642,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_export(ira, (IrInstructionExport *)instruction); case IrInstructionIdErrorReturnTrace: return ir_analyze_instruction_error_return_trace(ira, (IrInstructionErrorReturnTrace *)instruction); + case IrInstructionIdErrorUnion: + return ir_analyze_instruction_error_union(ira, (IrInstructionErrorUnion *)instruction); } zig_unreachable(); } @@ -15628,6 +16829,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdArgType: case IrInstructionIdTagType: case IrInstructionIdErrorReturnTrace: + case IrInstructionIdErrorUnion: return false; case IrInstructionIdAsm: { diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 8332212d34..f2c0d6a5b4 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -130,6 +130,8 @@ static const char *ir_bin_op_id_str(IrBinOp op_id) { return "++"; case IrBinOpArrayMult: return "**"; + case IrBinOpMergeErrorSets: + return "||"; } zig_unreachable(); } @@ -148,8 +150,6 @@ static const char *ir_un_op_id_str(IrUnOp op_id) { return "*"; case IrUnOpMaybe: return "?"; - case IrUnOpError: - return "%"; } zig_unreachable(); } @@ -1004,6 +1004,11 @@ static void ir_print_error_return_trace(IrPrint *irp, IrInstructionErrorReturnTr fprintf(irp->f, "@errorReturnTrace()"); } +static void ir_print_error_union(IrPrint *irp, IrInstructionErrorUnion *instruction) { + ir_print_other_instruction(irp, instruction->err_set); + fprintf(irp->f, "!"); + ir_print_other_instruction(irp, instruction->payload); +} static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { ir_print_prefix(irp, instruction); @@ -1322,6 +1327,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdErrorReturnTrace: ir_print_error_return_trace(irp, (IrInstructionErrorReturnTrace *)instruction); break; + case IrInstructionIdErrorUnion: + ir_print_error_union(irp, (IrInstructionErrorUnion *)instruction); + break; } fprintf(irp->f, "\n"); } diff --git a/src/main.cpp b/src/main.cpp index e3ef62be07..eab7f29b10 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,6 +66,7 @@ static int usage(const char *arg0) { " --msvc-lib-dir [path] (windows) directory where vcruntime.lib resides\n" " --kernel32-lib-dir [path] (windows) directory where kernel32.lib resides\n" " --library [lib] link against lib\n" + " --forbid-library [lib] make it an error to link against lib\n" " --library-path [dir] add a directory to the library search path\n" " --linker-script [path] use a custom linker script\n" " --object [obj] add object file to build\n" @@ -309,6 +310,7 @@ int main(int argc, char **argv) { ZigList llvm_argv = {0}; ZigList lib_dirs = {0}; ZigList link_libs = {0}; + ZigList forbidden_link_libs = {0}; ZigList frameworks = {0}; int err; const char *target_arch = nullptr; @@ -339,6 +341,7 @@ int main(int argc, char **argv) { const char *zig_exe_path = arg0; const char *build_file = "build.zig"; bool asked_for_help = false; + bool asked_to_init = false; init_all_targets(); @@ -350,6 +353,9 @@ int main(int argc, char **argv) { if (strcmp(argv[i], "--help") == 0) { asked_for_help = true; args.append(argv[i]); + } else if (strcmp(argv[i], "--init") == 0) { + asked_to_init = true; + args.append(argv[i]); } else if (i + 1 < argc && strcmp(argv[i], "--build-file") == 0) { build_file = argv[i + 1]; i += 1; @@ -414,6 +420,7 @@ int main(int argc, char **argv) { "\n" "General Options:\n" " --help Print this help and exit\n" + " --init Generate a build.zig template\n" " --build-file [file] Override path to build.zig\n" " --cache-dir [path] Override path to cache directory\n" " --verbose Print commands before executing them\n" @@ -426,7 +433,6 @@ int main(int argc, char **argv) { " --prefix [path] Override default install prefix\n" "\n" "Project-specific options become available when the build file is found.\n" - "Run this command with no options to generate a build.zig template.\n" "\n" "Advanced Options:\n" " --build-file [file] Override path to build.zig\n" @@ -439,17 +445,26 @@ int main(int argc, char **argv) { " --verbose-cimport Enable compiler debug output for C imports\n" "\n" , zig_exe_path); - return 0; - } - Buf *build_template_path = buf_alloc(); - os_path_join(special_dir, buf_create_from_str("build_file_template.zig"), build_template_path); + return EXIT_SUCCESS; + } else if (asked_to_init) { + Buf *build_template_path = buf_alloc(); + os_path_join(special_dir, buf_create_from_str("build_file_template.zig"), build_template_path); - if ((err = os_copy_file(build_template_path, &build_file_abs))) { - fprintf(stderr, "Unable to write build.zig template: %s\n", err_str(err)); - } else { - fprintf(stderr, "Wrote build.zig template\n"); + if ((err = os_copy_file(build_template_path, &build_file_abs))) { + fprintf(stderr, "Unable to write build.zig template: %s\n", err_str(err)); + } else { + fprintf(stderr, "Wrote build.zig template\n"); + } + return EXIT_SUCCESS; } - return 1; + + fprintf(stderr, + "No 'build.zig' file found.\n" + "Initialize a 'build.zig' template file with `zig build --init`,\n" + "or build an executable directly with `zig build-exe $FILENAME.zig`.\n" + "See: `zig build --help` or `zig help` for more options.\n" + ); + return EXIT_FAILURE; } PackageTableEntry *build_pkg = codegen_create_package(g, buf_ptr(&build_file_dirname), @@ -592,6 +607,8 @@ int main(int argc, char **argv) { lib_dirs.append(argv[i]); } else if (strcmp(arg, "--library") == 0) { link_libs.append(argv[i]); + } else if (strcmp(arg, "--forbid-library") == 0) { + forbidden_link_libs.append(argv[i]); } else if (strcmp(arg, "--object") == 0) { objects.append(argv[i]); } else if (strcmp(arg, "--assembly") == 0) { @@ -804,6 +821,10 @@ int main(int argc, char **argv) { LinkLib *link_lib = codegen_add_link_lib(g, buf_create_from_str(link_libs.at(i))); link_lib->provided_explicitly = true; } + for (size_t i = 0; i < forbidden_link_libs.length; i += 1) { + Buf *forbidden_link_lib = buf_create_from_str(forbidden_link_libs.at(i)); + codegen_add_forbidden_lib(g, forbidden_link_lib); + } for (size_t i = 0; i < frameworks.length; i += 1) { codegen_add_framework(g, frameworks.at(i)); } diff --git a/src/parser.cpp b/src/parser.cpp index 12293bc61b..6ce9e25221 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -221,6 +221,7 @@ static AstNode *ast_parse_grouped_expr(ParseContext *pc, size_t *token_index, bo static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index, bool mandatory); static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bool mandatory); static AstNode *ast_parse_try_expr(ParseContext *pc, size_t *token_index); +static AstNode *ast_parse_symbol(ParseContext *pc, size_t *token_index); static void ast_expect_token(ParseContext *pc, Token *token, TokenId token_id) { if (token->id == token_id) { @@ -240,7 +241,28 @@ static Token *ast_eat_token(ParseContext *pc, size_t *token_index, TokenId token } /* -TypeExpr = PrefixOpExpression | "var" +ErrorSetExpr = (PrefixOpExpression "!" PrefixOpExpression) | PrefixOpExpression +*/ +static AstNode *ast_parse_error_set_expr(ParseContext *pc, size_t *token_index, bool mandatory) { + AstNode *prefix_op_expr = ast_parse_prefix_op_expr(pc, token_index, mandatory); + if (!prefix_op_expr) { + return nullptr; + } + Token *token = &pc->tokens->at(*token_index); + if (token->id == TokenIdBang) { + *token_index += 1; + AstNode *node = ast_create_node(pc, NodeTypeBinOpExpr, token); + node->data.bin_op_expr.op1 = prefix_op_expr; + node->data.bin_op_expr.bin_op = BinOpTypeErrorUnion; + node->data.bin_op_expr.op2 = ast_parse_prefix_op_expr(pc, token_index, true); + return node; + } else { + return prefix_op_expr; + } +} + +/* +TypeExpr = ErrorSetExpr | "var" */ static AstNode *ast_parse_type_expr(ParseContext *pc, size_t *token_index, bool mandatory) { Token *token = &pc->tokens->at(*token_index); @@ -249,7 +271,7 @@ static AstNode *ast_parse_type_expr(ParseContext *pc, size_t *token_index, bool *token_index += 1; return node; } else { - return ast_parse_prefix_op_expr(pc, token_index, mandatory); + return ast_parse_error_set_expr(pc, token_index, mandatory); } } @@ -651,8 +673,9 @@ static AstNode *ast_parse_comptime_expr(ParseContext *pc, size_t *token_index, b } /* -PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." Symbol) | ContainerDecl | ("continue" option(":" Symbol)) +PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ContainerDecl | ("continue" option(":" Symbol)) | ErrorSetDecl KeywordLiteral = "true" | "false" | "null" | "undefined" | "error" | "this" | "unreachable" +ErrorSetDecl = "error" "{" list(Symbol, ",") "}" */ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bool mandatory) { Token *token = &pc->tokens->at(*token_index); @@ -716,9 +739,31 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo *token_index += 1; return node; } else if (token->id == TokenIdKeywordError) { - AstNode *node = ast_create_node(pc, NodeTypeErrorType, token); - *token_index += 1; - return node; + Token *next_token = &pc->tokens->at(*token_index + 1); + if (next_token->id == TokenIdLBrace) { + AstNode *node = ast_create_node(pc, NodeTypeErrorSetDecl, token); + *token_index += 2; + for (;;) { + Token *item_tok = &pc->tokens->at(*token_index); + if (item_tok->id == TokenIdRBrace) { + *token_index += 1; + return node; + } else if (item_tok->id == TokenIdSymbol) { + AstNode *symbol_node = ast_parse_symbol(pc, token_index); + node->data.err_set_decl.decls.append(symbol_node); + Token *opt_comma_tok = &pc->tokens->at(*token_index); + if (opt_comma_tok->id == TokenIdComma) { + *token_index += 1; + } + } else { + ast_invalid_token_error(pc, item_tok); + } + } + } else { + AstNode *node = ast_create_node(pc, NodeTypeErrorType, token); + *token_index += 1; + return node; + } } else if (token->id == TokenIdAtSign) { *token_index += 1; Token *name_tok = &pc->tokens->at(*token_index); @@ -950,7 +995,6 @@ static PrefixOp tok_to_prefix_op(Token *token) { case TokenIdTilde: return PrefixOpBinNot; case TokenIdStar: return PrefixOpDereference; case TokenIdMaybe: return PrefixOpMaybe; - case TokenIdPercent: return PrefixOpError; case TokenIdDoubleQuestion: return PrefixOpUnwrapMaybe; case TokenIdStarStar: return PrefixOpDereference; default: return PrefixOpInvalid; @@ -997,8 +1041,8 @@ static AstNode *ast_parse_addr_of(ParseContext *pc, size_t *token_index) { } /* -PrefixOpExpression : PrefixOp PrefixOpExpression | SuffixOpExpression -PrefixOp = "!" | "-" | "~" | "*" | ("&" option("align" "(" Expression option(":" Integer ":" Integer) ")" ) option("const") option("volatile")) | "?" | "%" | "%%" | "??" | "-%" | "try" +PrefixOpExpression = PrefixOp ErrorSetExpr | SuffixOpExpression +PrefixOp = "!" | "-" | "~" | "*" | ("&" option("align" "(" Expression option(":" Integer ":" Integer) ")" ) option("const") option("volatile")) | "?" | "??" | "-%" | "try" */ static AstNode *ast_parse_prefix_op_expr(ParseContext *pc, size_t *token_index, bool mandatory) { Token *token = &pc->tokens->at(*token_index); @@ -1028,7 +1072,7 @@ static AstNode *ast_parse_prefix_op_expr(ParseContext *pc, size_t *token_index, node->column += 1; } - AstNode *prefix_op_expr = ast_parse_prefix_op_expr(pc, token_index, true); + AstNode *prefix_op_expr = ast_parse_error_set_expr(pc, token_index, true); node->data.prefix_op_expr.primary_expr = prefix_op_expr; node->data.prefix_op_expr.prefix_op = prefix_op; @@ -1043,12 +1087,14 @@ static BinOpType tok_to_mult_op(Token *token) { case TokenIdStarStar: return BinOpTypeArrayMult; case TokenIdSlash: return BinOpTypeDiv; case TokenIdPercent: return BinOpTypeMod; + case TokenIdBang: return BinOpTypeErrorUnion; + case TokenIdBarBar: return BinOpTypeMergeErrorSets; default: return BinOpTypeInvalid; } } /* -MultiplyOperator = "*" | "/" | "%" | "**" | "*%" +MultiplyOperator = "||" | "*" | "/" | "%" | "**" | "*%" */ static BinOpType ast_parse_mult_op(ParseContext *pc, size_t *token_index, bool mandatory) { Token *token = &pc->tokens->at(*token_index); @@ -2240,7 +2286,7 @@ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mand } /* -FnProto = option("nakedcc" | "stdcallcc" | "extern") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") TypeExpr +FnProto = option("nakedcc" | "stdcallcc" | "extern") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("!") TypeExpr */ static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool mandatory, VisibMod visib_mod) { Token *first_token = &pc->tokens->at(*token_index); @@ -2315,6 +2361,18 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool m ast_eat_token(pc, token_index, TokenIdRParen); next_token = &pc->tokens->at(*token_index); } + if (next_token->id == TokenIdKeywordError) { + Token *maybe_lbrace_tok = &pc->tokens->at(*token_index + 1); + if (maybe_lbrace_tok->id == TokenIdLBrace) { + *token_index += 1; + node->data.fn_proto.return_type = ast_create_node(pc, NodeTypeErrorType, next_token); + return node; + } + } else if (next_token->id == TokenIdBang) { + *token_index += 1; + node->data.fn_proto.auto_err_set = true; + next_token = &pc->tokens->at(*token_index); + } node->data.fn_proto.return_type = ast_parse_type_expr(pc, token_index, true); return node; @@ -2531,7 +2589,7 @@ static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index, Token *colon_token = &pc->tokens->at(*token_index); if (colon_token->id == TokenIdColon) { *token_index += 1; - field_node->data.struct_field.type = ast_parse_prefix_op_expr(pc, token_index, true); + field_node->data.struct_field.type = ast_parse_type_expr(pc, token_index, true); } Token *eq_token = &pc->tokens->at(*token_index); if (eq_token->id == TokenIdEq) { @@ -2559,26 +2617,6 @@ static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index, return node; } -/* -ErrorValueDecl : "error" "Symbol" ";" -*/ -static AstNode *ast_parse_error_value_decl(ParseContext *pc, size_t *token_index) { - Token *first_token = &pc->tokens->at(*token_index); - - if (first_token->id != TokenIdKeywordError) { - return nullptr; - } - *token_index += 1; - - Token *name_tok = ast_eat_token(pc, token_index, TokenIdSymbol); - ast_eat_token(pc, token_index, TokenIdSemicolon); - - AstNode *node = ast_create_node(pc, NodeTypeErrorValueDecl, first_token); - node->data.error_value_decl.name = token_buf(name_tok); - - return node; -} - /* TestDecl = "test" String Block */ @@ -2611,12 +2649,6 @@ static void ast_parse_top_level_decls(ParseContext *pc, size_t *token_index, Zig continue; } - AstNode *error_value_node = ast_parse_error_value_decl(pc, token_index); - if (error_value_node) { - top_level_decls->append(error_value_node); - continue; - } - AstNode *test_decl_node = ast_parse_test_decl_node(pc, token_index); if (test_decl_node) { top_level_decls->append(test_decl_node); @@ -2744,9 +2776,6 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont visit_field(&node->data.variable_declaration.align_expr, visit, context); visit_field(&node->data.variable_declaration.section_expr, visit, context); break; - case NodeTypeErrorValueDecl: - // none - break; case NodeTypeTestDecl: visit_field(&node->data.test_decl.body, visit, context); break; @@ -2899,5 +2928,8 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont visit_field(&node->data.addr_of_expr.align_expr, visit, context); visit_field(&node->data.addr_of_expr.op_expr, visit, context); break; + case NodeTypeErrorSetDecl: + visit_node_list(&node->data.err_set_decl.decls, visit, context); + break; } } diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index f98c0c8344..44d838a723 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -195,7 +195,8 @@ enum TokenizeState { TokenizeStateSawMinusPercent, TokenizeStateSawAmpersand, TokenizeStateSawCaret, - TokenizeStateSawPipe, + TokenizeStateSawBar, + TokenizeStateSawBarBar, TokenizeStateLineComment, TokenizeStateLineString, TokenizeStateLineStringEnd, @@ -594,7 +595,7 @@ void tokenize(Buf *buf, Tokenization *out) { break; case '|': begin_token(&t, TokenIdBinOr); - t.state = TokenizeStateSawPipe; + t.state = TokenizeStateSawBar; break; case '=': begin_token(&t, TokenIdEq); @@ -888,20 +889,37 @@ void tokenize(Buf *buf, Tokenization *out) { continue; } break; - case TokenizeStateSawPipe: + case TokenizeStateSawBar: switch (c) { case '=': set_token_id(&t, t.cur_tok, TokenIdBitOrEq); end_token(&t); t.state = TokenizeStateStart; break; + case '|': + set_token_id(&t, t.cur_tok, TokenIdBarBar); + t.state = TokenizeStateSawBarBar; + break; + default: + t.pos -= 1; + end_token(&t); + t.state = TokenizeStateStart; + continue; + } + break; + case TokenizeStateSawBarBar: + switch (c) { + case '=': + set_token_id(&t, t.cur_tok, TokenIdBarBarEq); + end_token(&t); + t.state = TokenizeStateStart; + break; default: t.pos -= 1; end_token(&t); t.state = TokenizeStateStart; continue; } - break; case TokenizeStateSawSlash: switch (c) { case '/': @@ -1428,7 +1446,7 @@ void tokenize(Buf *buf, Tokenization *out) { case TokenizeStateSawDash: case TokenizeStateSawAmpersand: case TokenizeStateSawCaret: - case TokenizeStateSawPipe: + case TokenizeStateSawBar: case TokenizeStateSawEq: case TokenizeStateSawBang: case TokenizeStateSawLessThan: @@ -1443,6 +1461,7 @@ void tokenize(Buf *buf, Tokenization *out) { case TokenizeStateSawMinusPercent: case TokenizeStateLineString: case TokenizeStateLineStringEnd: + case TokenizeStateSawBarBar: end_token(&t); break; case TokenizeStateSawDotDot: @@ -1475,6 +1494,7 @@ const char * token_name(TokenId id) { case TokenIdArrow: return "->"; case TokenIdAtSign: return "@"; case TokenIdBang: return "!"; + case TokenIdBarBar: return "||"; case TokenIdBinOr: return "|"; case TokenIdBinXor: return "^"; case TokenIdBitAndEq: return "&="; @@ -1577,6 +1597,7 @@ const char * token_name(TokenId id) { case TokenIdTimesEq: return "*="; case TokenIdTimesPercent: return "*%"; case TokenIdTimesPercentEq: return "*%="; + case TokenIdBarBarEq: return "||="; } return "(invalid token)"; } diff --git a/src/tokenizer.hpp b/src/tokenizer.hpp index 749f72f419..92a3b8de0d 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -17,6 +17,8 @@ enum TokenId { TokenIdArrow, TokenIdAtSign, TokenIdBang, + TokenIdBarBar, + TokenIdBarBarEq, TokenIdBinOr, TokenIdBinXor, TokenIdBitAndEq, diff --git a/src/util.hpp b/src/util.hpp index ce6cc09a59..ae33cb84af 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -91,20 +91,23 @@ static inline void safe_memcpy(T *dest, const T *src, size_t count) { #endif } +template +static inline T *reallocate(T *old, size_t old_count, size_t new_count) { + T *ptr = reinterpret_cast(realloc(old, new_count * sizeof(T))); + if (!ptr) + zig_panic("allocation failed"); + if (new_count > old_count) { + memset(&ptr[old_count], 0, (new_count - old_count) * sizeof(T)); + } + return ptr; +} + template static inline T *reallocate_nonzero(T *old, size_t old_count, size_t new_count) { -#ifdef NDEBUG T *ptr = reinterpret_cast(realloc(old, new_count * sizeof(T))); if (!ptr) zig_panic("allocation failed"); return ptr; -#else - // manually assign every element to trigger compile error for non-copyable structs - T *ptr = allocate_nonzero(new_count); - safe_memcpy(ptr, old, old_count); - free(old); - return ptr; -#endif } template diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index a33303a9ea..e92cfcd44b 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -437,6 +437,10 @@ unsigned ZigLLVMTag_DW_structure_type(void) { return dwarf::DW_TAG_structure_type; } +unsigned ZigLLVMTag_DW_enumeration_type(void) { + return dwarf::DW_TAG_enumeration_type; +} + unsigned ZigLLVMTag_DW_union_type(void) { return dwarf::DW_TAG_union_type; } diff --git a/src/zig_llvm.h b/src/zig_llvm.h index 9cdb28dc58..ac2fe01bfc 100644 --- a/src/zig_llvm.h +++ b/src/zig_llvm.h @@ -133,6 +133,7 @@ ZIG_EXTERN_C unsigned ZigLLVMEncoding_DW_ATE_signed_char(void); ZIG_EXTERN_C unsigned ZigLLVMLang_DW_LANG_C99(void); ZIG_EXTERN_C unsigned ZigLLVMTag_DW_variable(void); ZIG_EXTERN_C unsigned ZigLLVMTag_DW_structure_type(void); +ZIG_EXTERN_C unsigned ZigLLVMTag_DW_enumeration_type(void); ZIG_EXTERN_C unsigned ZigLLVMTag_DW_union_type(void); ZIG_EXTERN_C struct ZigLLVMDIBuilder *ZigLLVMCreateDIBuilder(LLVMModuleRef module, bool allow_unresolved); diff --git a/std/array_list.zig b/std/array_list.zig index bc4d3c1d81..2a44b66518 100644 --- a/std/array_list.zig +++ b/std/array_list.zig @@ -40,6 +40,10 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type{ return l.items[0..l.len]; } + pub fn at(l: &const Self, n: usize) T { + return l.toSliceConst()[n]; + } + /// ArrayList takes ownership of the passed in slice. The slice must have been /// allocated with `allocator`. /// Deinitialize with `deinit` or use `toOwnedSlice`. @@ -59,18 +63,34 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type{ return result; } - pub fn append(l: &Self, item: &const T) %void { + pub fn insert(l: &Self, n: usize, item: &const T) !void { + try l.ensureCapacity(l.len + 1); + l.len += 1; + + mem.copy(T, l.items[n+1..l.len], l.items[n..l.len-1]); + l.items[n] = *item; + } + + pub fn insertSlice(l: &Self, n: usize, items: []align(A) const T) !void { + try l.ensureCapacity(l.len + items.len); + l.len += items.len; + + mem.copy(T, l.items[n+items.len..l.len], l.items[n..l.len-items.len]); + mem.copy(T, l.items[n..n+items.len], items); + } + + pub fn append(l: &Self, item: &const T) !void { const new_item_ptr = try l.addOne(); *new_item_ptr = *item; } - pub fn appendSlice(l: &Self, items: []align(A) const T) %void { + pub fn appendSlice(l: &Self, items: []align(A) const T) !void { try l.ensureCapacity(l.len + items.len); mem.copy(T, l.items[l.len..], items); l.len += items.len; } - pub fn resize(l: &Self, new_len: usize) %void { + pub fn resize(l: &Self, new_len: usize) !void { try l.ensureCapacity(new_len); l.len = new_len; } @@ -80,7 +100,7 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type{ l.len = new_len; } - pub fn ensureCapacity(l: &Self, new_capacity: usize) %void { + pub fn ensureCapacity(l: &Self, new_capacity: usize) !void { var better_capacity = l.items.len; if (better_capacity >= new_capacity) return; while (true) { @@ -90,7 +110,7 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type{ l.items = try l.allocator.alignedRealloc(T, A, l.items, better_capacity); } - pub fn addOne(l: &Self) %&T { + pub fn addOne(l: &Self) !&T { const new_length = l.len + 1; try l.ensureCapacity(new_length); const result = &l.items[l.len]; @@ -136,3 +156,22 @@ test "basic ArrayList test" { list.appendSlice([]const i32 {}) catch unreachable; assert(list.len == 9); } + +test "insert ArrayList test" { + var list = ArrayList(i32).init(debug.global_allocator); + defer list.deinit(); + + try list.append(1); + try list.insert(0, 5); + assert(list.items[0] == 5); + assert(list.items[1] == 1); + + try list.insertSlice(1, []const i32 { 9, 8 }); + assert(list.items[0] == 5); + assert(list.items[1] == 9); + assert(list.items[2] == 8); + + const items = []const i32 { 1 }; + try list.insertSlice(0, items[0..0]); + assert(list.items[0] == 5); +} diff --git a/std/base64.zig b/std/base64.zig index 8cd89b67b5..d9e1d2f908 100644 --- a/std/base64.zig +++ b/std/base64.zig @@ -79,8 +79,6 @@ pub const Base64Encoder = struct { }; pub const standard_decoder = Base64Decoder.init(standard_alphabet_chars, standard_pad_char); -error InvalidPadding; -error InvalidCharacter; pub const Base64Decoder = struct { /// e.g. 'A' => 0. @@ -111,7 +109,7 @@ pub const Base64Decoder = struct { } /// If the encoded buffer is detected to be invalid, returns error.InvalidPadding. - pub fn calcSize(decoder: &const Base64Decoder, source: []const u8) %usize { + pub fn calcSize(decoder: &const Base64Decoder, source: []const u8) !usize { if (source.len % 4 != 0) return error.InvalidPadding; return calcDecodedSizeExactUnsafe(source, decoder.pad_char); } @@ -119,7 +117,7 @@ pub const Base64Decoder = struct { /// dest.len must be what you get from ::calcSize. /// invalid characters result in error.InvalidCharacter. /// invalid padding results in error.InvalidPadding. - pub fn decode(decoder: &const Base64Decoder, dest: []u8, source: []const u8) %void { + pub fn decode(decoder: &const Base64Decoder, dest: []u8, source: []const u8) !void { assert(dest.len == (decoder.calcSize(source) catch unreachable)); assert(source.len % 4 == 0); @@ -163,8 +161,6 @@ pub const Base64Decoder = struct { } }; -error OutputTooSmall; - pub const Base64DecoderWithIgnore = struct { decoder: Base64Decoder, char_is_ignored: [256]bool, @@ -185,7 +181,7 @@ pub const Base64DecoderWithIgnore = struct { } /// If no characters end up being ignored or padding, this will be the exact decoded size. - pub fn calcSizeUpperBound(encoded_len: usize) %usize { + pub fn calcSizeUpperBound(encoded_len: usize) usize { return @divTrunc(encoded_len, 4) * 3; } @@ -193,7 +189,7 @@ pub const Base64DecoderWithIgnore = struct { /// Invalid padding results in error.InvalidPadding. /// Decoding more data than can fit in dest results in error.OutputTooSmall. See also ::calcSizeUpperBound. /// Returns the number of bytes writen to dest. - pub fn decode(decoder_with_ignore: &const Base64DecoderWithIgnore, dest: []u8, source: []const u8) %usize { + pub fn decode(decoder_with_ignore: &const Base64DecoderWithIgnore, dest: []u8, source: []const u8) !usize { const decoder = &decoder_with_ignore.decoder; var src_cursor: usize = 0; @@ -378,7 +374,7 @@ test "base64" { comptime (testBase64() catch unreachable); } -fn testBase64() %void { +fn testBase64() !void { try testAllApis("", ""); try testAllApis("f", "Zg=="); try testAllApis("fo", "Zm8="); @@ -412,7 +408,7 @@ fn testBase64() %void { try testOutputTooSmallError("AAAAAA=="); } -fn testAllApis(expected_decoded: []const u8, expected_encoded: []const u8) %void { +fn testAllApis(expected_decoded: []const u8, expected_encoded: []const u8) !void { // Base64Encoder { var buffer: [0x100]u8 = undefined; @@ -434,7 +430,7 @@ fn testAllApis(expected_decoded: []const u8, expected_encoded: []const u8) %void const standard_decoder_ignore_nothing = Base64DecoderWithIgnore.init( standard_alphabet_chars, standard_pad_char, ""); var buffer: [0x100]u8 = undefined; - var decoded = buffer[0..try Base64DecoderWithIgnore.calcSizeUpperBound(expected_encoded.len)]; + var decoded = buffer[0..Base64DecoderWithIgnore.calcSizeUpperBound(expected_encoded.len)]; var written = try standard_decoder_ignore_nothing.decode(decoded, expected_encoded); assert(written <= decoded.len); assert(mem.eql(u8, decoded[0..written], expected_decoded)); @@ -449,17 +445,16 @@ fn testAllApis(expected_decoded: []const u8, expected_encoded: []const u8) %void } } -fn testDecodeIgnoreSpace(expected_decoded: []const u8, encoded: []const u8) %void { +fn testDecodeIgnoreSpace(expected_decoded: []const u8, encoded: []const u8) !void { const standard_decoder_ignore_space = Base64DecoderWithIgnore.init( standard_alphabet_chars, standard_pad_char, " "); var buffer: [0x100]u8 = undefined; - var decoded = buffer[0..try Base64DecoderWithIgnore.calcSizeUpperBound(encoded.len)]; + var decoded = buffer[0..Base64DecoderWithIgnore.calcSizeUpperBound(encoded.len)]; var written = try standard_decoder_ignore_space.decode(decoded, encoded); assert(mem.eql(u8, decoded[0..written], expected_decoded)); } -error ExpectedError; -fn testError(encoded: []const u8, expected_err: error) %void { +fn testError(encoded: []const u8, expected_err: error) !void { const standard_decoder_ignore_space = Base64DecoderWithIgnore.init( standard_alphabet_chars, standard_pad_char, " "); var buffer: [0x100]u8 = undefined; @@ -475,7 +470,7 @@ fn testError(encoded: []const u8, expected_err: error) %void { } else |err| if (err != expected_err) return err; } -fn testOutputTooSmallError(encoded: []const u8) %void { +fn testOutputTooSmallError(encoded: []const u8) !void { const standard_decoder_ignore_space = Base64DecoderWithIgnore.init( standard_alphabet_chars, standard_pad_char, " "); var buffer: [0x100]u8 = undefined; diff --git a/std/buf_map.zig b/std/buf_map.zig index 15ffe785e6..d7f81cf2cc 100644 --- a/std/buf_map.zig +++ b/std/buf_map.zig @@ -27,7 +27,7 @@ pub const BufMap = struct { self.hash_map.deinit(); } - pub fn set(self: &BufMap, key: []const u8, value: []const u8) %void { + pub fn set(self: &BufMap, key: []const u8, value: []const u8) !void { if (self.hash_map.get(key)) |entry| { const value_copy = try self.copy(value); errdefer self.free(value_copy); @@ -67,7 +67,7 @@ pub const BufMap = struct { self.hash_map.allocator.free(mut_value); } - fn copy(self: &BufMap, value: []const u8) %[]const u8 { + fn copy(self: &BufMap, value: []const u8) ![]const u8 { const result = try self.hash_map.allocator.alloc(u8, value.len); mem.copy(u8, result, value); return result; diff --git a/std/buf_set.zig b/std/buf_set.zig index 2349c17433..4fa16762b6 100644 --- a/std/buf_set.zig +++ b/std/buf_set.zig @@ -24,7 +24,7 @@ pub const BufSet = struct { self.hash_map.deinit(); } - pub fn put(self: &BufSet, key: []const u8) %void { + pub fn put(self: &BufSet, key: []const u8) !void { if (self.hash_map.get(key) == null) { const key_copy = try self.copy(key); errdefer self.free(key_copy); @@ -55,7 +55,7 @@ pub const BufSet = struct { self.hash_map.allocator.free(mut_value); } - fn copy(self: &BufSet, value: []const u8) %[]const u8 { + fn copy(self: &BufSet, value: []const u8) ![]const u8 { const result = try self.hash_map.allocator.alloc(u8, value.len); mem.copy(u8, result, value); return result; diff --git a/std/buffer.zig b/std/buffer.zig index 34428aa8e4..e0892d5933 100644 --- a/std/buffer.zig +++ b/std/buffer.zig @@ -12,14 +12,14 @@ pub const Buffer = struct { list: ArrayList(u8), /// Must deinitialize with deinit. - pub fn init(allocator: &Allocator, m: []const u8) %Buffer { + pub fn init(allocator: &Allocator, m: []const u8) !Buffer { var self = try initSize(allocator, m.len); mem.copy(u8, self.list.items, m); return self; } /// Must deinitialize with deinit. - pub fn initSize(allocator: &Allocator, size: usize) %Buffer { + pub fn initSize(allocator: &Allocator, size: usize) !Buffer { var self = initNull(allocator); try self.resize(size); return self; @@ -37,7 +37,7 @@ pub const Buffer = struct { } /// Must deinitialize with deinit. - pub fn initFromBuffer(buffer: &const Buffer) %Buffer { + pub fn initFromBuffer(buffer: &const Buffer) !Buffer { return Buffer.init(buffer.list.allocator, buffer.toSliceConst()); } @@ -80,7 +80,7 @@ pub const Buffer = struct { self.list.items[self.len()] = 0; } - pub fn resize(self: &Buffer, new_len: usize) %void { + pub fn resize(self: &Buffer, new_len: usize) !void { try self.list.resize(new_len + 1); self.list.items[self.len()] = 0; } @@ -93,24 +93,24 @@ pub const Buffer = struct { return self.list.len - 1; } - pub fn append(self: &Buffer, m: []const u8) %void { + pub fn append(self: &Buffer, m: []const u8) !void { const old_len = self.len(); try self.resize(old_len + m.len); mem.copy(u8, self.list.toSlice()[old_len..], m); } // TODO: remove, use OutStream for this - pub fn appendFormat(self: &Buffer, comptime format: []const u8, args: ...) %void { + pub fn appendFormat(self: &Buffer, comptime format: []const u8, args: ...) !void { return fmt.format(self, append, format, args); } // TODO: remove, use OutStream for this - pub fn appendByte(self: &Buffer, byte: u8) %void { + pub fn appendByte(self: &Buffer, byte: u8) !void { return self.appendByteNTimes(byte, 1); } // TODO: remove, use OutStream for this - pub fn appendByteNTimes(self: &Buffer, byte: u8, count: usize) %void { + pub fn appendByteNTimes(self: &Buffer, byte: u8, count: usize) !void { var prev_size: usize = self.len(); const new_size = prev_size + count; try self.resize(new_size); @@ -137,7 +137,7 @@ pub const Buffer = struct { return mem.eql(u8, self.list.items[start..l], m); } - pub fn replaceContents(self: &const Buffer, m: []const u8) %void { + pub fn replaceContents(self: &const Buffer, m: []const u8) !void { try self.resize(m.len); mem.copy(u8, self.list.toSlice(), m); } diff --git a/std/build.zig b/std/build.zig index 6c56988896..e6b6676261 100644 --- a/std/build.zig +++ b/std/build.zig @@ -15,13 +15,6 @@ const BufSet = std.BufSet; const BufMap = std.BufMap; const fmt_lib = std.fmt; -error ExtraArg; -error UncleanExit; -error InvalidStepName; -error DependencyLoopDetected; -error NoCompilerFound; -error NeedAnObject; - pub const Builder = struct { uninstall_tls: TopLevelStep, install_tls: TopLevelStep, @@ -242,7 +235,7 @@ pub const Builder = struct { self.lib_paths.append(path) catch unreachable; } - pub fn make(self: &Builder, step_names: []const []const u8) %void { + pub fn make(self: &Builder, step_names: []const []const u8) !void { var wanted_steps = ArrayList(&Step).init(self.allocator); defer wanted_steps.deinit(); @@ -278,7 +271,7 @@ pub const Builder = struct { return &self.uninstall_tls.step; } - fn makeUninstall(uninstall_step: &Step) %void { + fn makeUninstall(uninstall_step: &Step) error!void { const uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step); const self = @fieldParentPtr(Builder, "uninstall_tls", uninstall_tls); @@ -292,7 +285,7 @@ pub const Builder = struct { // TODO remove empty directories } - fn makeOneStep(self: &Builder, s: &Step) %void { + fn makeOneStep(self: &Builder, s: &Step) error!void { if (s.loop_flag) { warn("Dependency loop detected:\n {}\n", s.name); return error.DependencyLoopDetected; @@ -313,7 +306,7 @@ pub const Builder = struct { try s.make(); } - fn getTopLevelStepByName(self: &Builder, name: []const u8) %&Step { + fn getTopLevelStepByName(self: &Builder, name: []const u8) !&Step { for (self.top_level_steps.toSliceConst()) |top_level_step| { if (mem.eql(u8, top_level_step.step.name, name)) { return &top_level_step.step; @@ -548,7 +541,7 @@ pub const Builder = struct { return self.invalid_user_input; } - fn spawnChild(self: &Builder, argv: []const []const u8) %void { + fn spawnChild(self: &Builder, argv: []const []const u8) !void { return self.spawnChildEnvMap(null, &self.env_map, argv); } @@ -561,7 +554,7 @@ pub const Builder = struct { } fn spawnChildEnvMap(self: &Builder, cwd: ?[]const u8, env_map: &const BufMap, - argv: []const []const u8) %void + argv: []const []const u8) !void { if (self.verbose) { printCmd(cwd, argv); @@ -595,7 +588,7 @@ pub const Builder = struct { } } - pub fn makePath(self: &Builder, path: []const u8) %void { + pub fn makePath(self: &Builder, path: []const u8) !void { os.makePath(self.allocator, self.pathFromRoot(path)) catch |err| { warn("Unable to create path {}: {}\n", path, @errorName(err)); return err; @@ -630,11 +623,11 @@ pub const Builder = struct { self.installed_files.append(full_path) catch unreachable; } - fn copyFile(self: &Builder, source_path: []const u8, dest_path: []const u8) %void { - return self.copyFileMode(source_path, dest_path, 0o666); + fn copyFile(self: &Builder, source_path: []const u8, dest_path: []const u8) !void { + return self.copyFileMode(source_path, dest_path, os.default_file_mode); } - fn copyFileMode(self: &Builder, source_path: []const u8, dest_path: []const u8, mode: usize) %void { + fn copyFileMode(self: &Builder, source_path: []const u8, dest_path: []const u8, mode: os.FileMode) !void { if (self.verbose) { warn("cp {} {}\n", source_path, dest_path); } @@ -672,7 +665,7 @@ pub const Builder = struct { } } - pub fn findProgram(self: &Builder, names: []const []const u8, paths: []const []const u8) %[]const u8 { + pub fn findProgram(self: &Builder, names: []const []const u8, paths: []const []const u8) ![]const u8 { // TODO report error for ambiguous situations const exe_extension = (Target { .Native = {}}).exeFileExt(); for (self.search_prefixes.toSliceConst()) |search_prefix| { @@ -721,7 +714,7 @@ pub const Builder = struct { return error.FileNotFound; } - pub fn exec(self: &Builder, argv: []const []const u8) %[]u8 { + pub fn exec(self: &Builder, argv: []const []const u8) ![]u8 { const max_output_size = 100 * 1024; const result = try os.ChildProcess.exec(self.allocator, argv, null, null, max_output_size); switch (result.term) { @@ -1180,12 +1173,12 @@ pub const LibExeObjStep = struct { self.disable_libc = disable; } - fn make(step: &Step) %void { + fn make(step: &Step) !void { const self = @fieldParentPtr(LibExeObjStep, "step", step); return if (self.is_zig) self.makeZig() else self.makeC(); } - fn makeZig(self: &LibExeObjStep) %void { + fn makeZig(self: &LibExeObjStep) !void { const builder = self.builder; assert(self.is_zig); @@ -1396,7 +1389,7 @@ pub const LibExeObjStep = struct { } } - fn makeC(self: &LibExeObjStep) %void { + fn makeC(self: &LibExeObjStep) !void { const builder = self.builder; const cc = builder.getCCExe(); @@ -1687,7 +1680,7 @@ pub const TestStep = struct { self.exec_cmd_args = args; } - fn make(step: &Step) %void { + fn make(step: &Step) !void { const self = @fieldParentPtr(TestStep, "step", step); const builder = self.builder; @@ -1796,7 +1789,7 @@ pub const CommandStep = struct { return self; } - fn make(step: &Step) %void { + fn make(step: &Step) !void { const self = @fieldParentPtr(CommandStep, "step", step); const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root; @@ -1836,14 +1829,17 @@ const InstallArtifactStep = struct { return self; } - fn make(step: &Step) %void { + fn make(step: &Step) !void { const self = @fieldParentPtr(Self, "step", step); const builder = self.builder; - const mode = switch (self.artifact.kind) { - LibExeObjStep.Kind.Obj => unreachable, - LibExeObjStep.Kind.Exe => usize(0o755), - LibExeObjStep.Kind.Lib => if (self.artifact.static) usize(0o666) else usize(0o755), + const mode = switch (builtin.os) { + builtin.Os.windows => {}, + else => switch (self.artifact.kind) { + LibExeObjStep.Kind.Obj => unreachable, + LibExeObjStep.Kind.Exe => u32(0o755), + LibExeObjStep.Kind.Lib => if (self.artifact.static) u32(0o666) else u32(0o755), + }, }; try builder.copyFileMode(self.artifact.getOutputPath(), self.dest_file, mode); if (self.artifact.kind == LibExeObjStep.Kind.Lib and !self.artifact.static) { @@ -1868,7 +1864,7 @@ pub const InstallFileStep = struct { }; } - fn make(step: &Step) %void { + fn make(step: &Step) !void { const self = @fieldParentPtr(InstallFileStep, "step", step); try self.builder.copyFile(self.src_path, self.dest_path); } @@ -1889,7 +1885,7 @@ pub const WriteFileStep = struct { }; } - fn make(step: &Step) %void { + fn make(step: &Step) !void { const self = @fieldParentPtr(WriteFileStep, "step", step); const full_path = self.builder.pathFromRoot(self.file_path); const full_path_dir = os.path.dirname(full_path); @@ -1897,7 +1893,7 @@ pub const WriteFileStep = struct { warn("unable to make path {}: {}\n", full_path_dir, @errorName(err)); return err; }; - io.writeFile(full_path, self.data, self.builder.allocator) catch |err| { + io.writeFile(self.builder.allocator, full_path, self.data) catch |err| { warn("unable to write {}: {}\n", full_path, @errorName(err)); return err; }; @@ -1917,7 +1913,7 @@ pub const LogStep = struct { }; } - fn make(step: &Step) %void { + fn make(step: &Step) error!void { const self = @fieldParentPtr(LogStep, "step", step); warn("{}", self.data); } @@ -1936,7 +1932,7 @@ pub const RemoveDirStep = struct { }; } - fn make(step: &Step) %void { + fn make(step: &Step) !void { const self = @fieldParentPtr(RemoveDirStep, "step", step); const full_path = self.builder.pathFromRoot(self.dir_path); @@ -1949,12 +1945,12 @@ pub const RemoveDirStep = struct { pub const Step = struct { name: []const u8, - makeFn: fn(self: &Step) %void, + makeFn: fn(self: &Step) error!void, dependencies: ArrayList(&Step), loop_flag: bool, done_flag: bool, - pub fn init(name: []const u8, allocator: &Allocator, makeFn: fn (&Step)%void) Step { + pub fn init(name: []const u8, allocator: &Allocator, makeFn: fn (&Step)error!void) Step { return Step { .name = name, .makeFn = makeFn, @@ -1967,7 +1963,7 @@ pub const Step = struct { return init(name, allocator, makeNoOp); } - pub fn make(self: &Step) %void { + pub fn make(self: &Step) !void { if (self.done_flag) return; @@ -1979,11 +1975,11 @@ pub const Step = struct { self.dependencies.append(other) catch unreachable; } - fn makeNoOp(self: &Step) %void {} + fn makeNoOp(self: &Step) error!void {} }; fn doAtomicSymLinks(allocator: &Allocator, output_path: []const u8, filename_major_only: []const u8, - filename_name_only: []const u8) %void + filename_name_only: []const u8) !void { const out_dir = os.path.dirname(output_path); const out_basename = os.path.basename(output_path); diff --git a/std/c/index.zig b/std/c/index.zig index 7b34ccea82..24e24dc3d3 100644 --- a/std/c/index.zig +++ b/std/c/index.zig @@ -20,7 +20,7 @@ pub extern "c" fn open(path: &const u8, oflag: c_int, ...) c_int; pub extern "c" fn raise(sig: c_int) c_int; pub extern "c" fn read(fd: c_int, buf: &c_void, nbyte: usize) isize; pub extern "c" fn stat(noalias path: &const u8, noalias buf: &Stat) c_int; -pub extern "c" fn write(fd: c_int, buf: &const c_void, nbyte: usize) c_int; +pub extern "c" fn write(fd: c_int, buf: &const c_void, nbyte: usize) isize; pub extern "c" fn mmap(addr: ?&c_void, len: usize, prot: c_int, flags: c_int, fd: c_int, offset: isize) ?&c_void; pub extern "c" fn munmap(addr: &c_void, len: usize) c_int; diff --git a/std/crypto/throughput_test.zig b/std/crypto/throughput_test.zig index 1ebe64d5a4..60610411b5 100644 --- a/std/crypto/throughput_test.zig +++ b/std/crypto/throughput_test.zig @@ -18,7 +18,7 @@ const c = @cImport({ const Mb = 1024 * 1024; -pub fn main() %void { +pub fn main() !void { var stdout_file = try std.io.getStdOut(); var stdout_out_stream = std.io.FileOutStream.init(&stdout_file); const stdout = &stdout_out_stream.stream; diff --git a/std/cstr.zig b/std/cstr.zig index 987c6d3341..d396dcbce3 100644 --- a/std/cstr.zig +++ b/std/cstr.zig @@ -1,8 +1,15 @@ const std = @import("index.zig"); +const builtin = @import("builtin"); const debug = std.debug; const mem = std.mem; const assert = debug.assert; +pub const line_sep = switch (builtin.os) { + builtin.Os.windows => "\r\n", + else => "\n", +}; + + pub fn len(ptr: &const u8) usize { var count: usize = 0; while (ptr[count] != 0) : (count += 1) {} @@ -39,10 +46,9 @@ fn testCStrFnsImpl() void { assert(len(c"123456789") == 9); } -/// Returns a mutable slice with exactly the same size which is guaranteed to -/// have a null byte after it. +/// Returns a mutable slice with 1 more byte of length which is a null byte. /// Caller owns the returned memory. -pub fn addNullByte(allocator: &mem.Allocator, slice: []const u8) %[]u8 { +pub fn addNullByte(allocator: &mem.Allocator, slice: []const u8) ![]u8 { const result = try allocator.alloc(u8, slice.len + 1); mem.copy(u8, result, slice); result[slice.len] = 0; @@ -56,7 +62,7 @@ pub const NullTerminated2DArray = struct { /// Takes N lists of strings, concatenates the lists together, and adds a null terminator /// Caller must deinit result - pub fn fromSlices(allocator: &mem.Allocator, slices: []const []const []const u8) %NullTerminated2DArray { + pub fn fromSlices(allocator: &mem.Allocator, slices: []const []const []const u8) !NullTerminated2DArray { var new_len: usize = 1; // 1 for the list null var byte_count: usize = 0; for (slices) |slice| { diff --git a/std/debug/failing_allocator.zig b/std/debug/failing_allocator.zig index cc5a8bc045..f876b7902d 100644 --- a/std/debug/failing_allocator.zig +++ b/std/debug/failing_allocator.zig @@ -28,7 +28,7 @@ pub const FailingAllocator = struct { }; } - fn alloc(allocator: &mem.Allocator, n: usize, alignment: u29) %[]u8 { + fn alloc(allocator: &mem.Allocator, n: usize, alignment: u29) ![]u8 { const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); if (self.index == self.fail_index) { return error.OutOfMemory; @@ -39,7 +39,7 @@ pub const FailingAllocator = struct { return result; } - fn realloc(allocator: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: u29) %[]u8 { + fn realloc(allocator: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 { const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); if (new_size <= old_mem.len) { self.freed_bytes += old_mem.len - new_size; diff --git a/std/debug/index.zig b/std/debug/index.zig index ccf5f6d413..5426a197f2 100644 --- a/std/debug/index.zig +++ b/std/debug/index.zig @@ -10,26 +10,17 @@ const builtin = @import("builtin"); pub const FailingAllocator = @import("failing_allocator.zig").FailingAllocator; -error MissingDebugInfo; -error InvalidDebugInfo; -error UnsupportedDebugInfo; -error UnknownObjectFormat; -error TodoSupportCoffDebugInfo; -error TodoSupportMachoDebugInfo; -error TodoSupportCOFFDebugInfo; - - /// Tries to write to stderr, unbuffered, and ignores any error returned. /// Does not append a newline. /// TODO atomic/multithread support -var stderr_file: io.File = undefined; +var stderr_file: os.File = undefined; var stderr_file_out_stream: io.FileOutStream = undefined; -var stderr_stream: ?&io.OutStream = null; +var stderr_stream: ?&io.OutStream(io.FileOutStream.Error) = null; pub fn warn(comptime fmt: []const u8, args: ...) void { const stderr = getStderrStream() catch return; stderr.print(fmt, args) catch return; } -fn getStderrStream() %&io.OutStream { +fn getStderrStream() !&io.OutStream(io.FileOutStream.Error) { if (stderr_stream) |st| { return st; } else { @@ -42,7 +33,7 @@ fn getStderrStream() %&io.OutStream { } var self_debug_info: ?&ElfStackTrace = null; -pub fn getSelfDebugInfo() %&ElfStackTrace { +pub fn getSelfDebugInfo() !&ElfStackTrace { if (self_debug_info) |info| { return info; } else { @@ -149,11 +140,8 @@ const WHITE = "\x1b[37;1m"; const DIM = "\x1b[2m"; const RESET = "\x1b[0m"; -error PathNotFound; -error InvalidDebugInfo; - -pub fn writeStackTrace(stack_trace: &const builtin.StackTrace, out_stream: &io.OutStream, allocator: &mem.Allocator, - debug_info: &ElfStackTrace, tty_color: bool) %void +pub fn writeStackTrace(stack_trace: &const builtin.StackTrace, out_stream: var, allocator: &mem.Allocator, + debug_info: &ElfStackTrace, tty_color: bool) !void { var frame_index: usize = undefined; var frames_left: usize = undefined; @@ -174,8 +162,8 @@ pub fn writeStackTrace(stack_trace: &const builtin.StackTrace, out_stream: &io.O } } -pub fn writeCurrentStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, - debug_info: &ElfStackTrace, tty_color: bool, ignore_frame_count: usize) %void +pub fn writeCurrentStackTrace(out_stream: var, allocator: &mem.Allocator, + debug_info: &ElfStackTrace, tty_color: bool, ignore_frame_count: usize) !void { var ignored_count: usize = 0; @@ -191,7 +179,7 @@ pub fn writeCurrentStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocat } } -fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: &io.OutStream, address: usize) %void { +fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: var, address: usize) !void { if (builtin.os == builtin.Os.windows) { return error.UnsupportedDebugInfo; } @@ -221,7 +209,7 @@ fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: &io.OutStream, a try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n"); } } else |err| switch (err) { - error.EndOfFile, error.PathNotFound => {}, + error.EndOfFile => {}, else => return err, } } else |err| switch (err) { @@ -232,7 +220,7 @@ fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: &io.OutStream, a } } -pub fn openSelfDebugInfo(allocator: &mem.Allocator) %&ElfStackTrace { +pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace { switch (builtin.object_format) { builtin.ObjectFormat.elf => { const st = try allocator.create(ElfStackTrace); @@ -276,8 +264,8 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) %&ElfStackTrace { } } -fn printLineFromFile(allocator: &mem.Allocator, out_stream: &io.OutStream, line_info: &const LineInfo) %void { - var f = try io.File.openRead(line_info.file_name, allocator); +fn printLineFromFile(allocator: &mem.Allocator, out_stream: var, line_info: &const LineInfo) !void { + var f = try os.File.openRead(allocator, line_info.file_name); defer f.close(); // TODO fstat and make sure that the file has the correct size @@ -310,7 +298,7 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: &io.OutStream, line_ } pub const ElfStackTrace = struct { - self_exe_file: io.File, + self_exe_file: os.File, elf: elf.Elf, debug_info: &elf.SectionHeader, debug_abbrev: &elf.SectionHeader, @@ -324,7 +312,7 @@ pub const ElfStackTrace = struct { return self.abbrev_table_list.allocator; } - pub fn readString(self: &ElfStackTrace) %[]u8 { + pub fn readString(self: &ElfStackTrace) ![]u8 { var in_file_stream = io.FileInStream.init(&self.self_exe_file); const in_stream = &in_file_stream.stream; return readStringRaw(self.allocator(), in_stream); @@ -387,7 +375,7 @@ const Constant = struct { payload: []u8, signed: bool, - fn asUnsignedLe(self: &const Constant) %u64 { + fn asUnsignedLe(self: &const Constant) !u64 { if (self.payload.len > @sizeOf(u64)) return error.InvalidDebugInfo; if (self.signed) @@ -414,7 +402,7 @@ const Die = struct { return null; } - fn getAttrAddr(self: &const Die, id: u64) %u64 { + fn getAttrAddr(self: &const Die, id: u64) !u64 { const form_value = self.getAttr(id) ?? return error.MissingDebugInfo; return switch (*form_value) { FormValue.Address => |value| value, @@ -422,7 +410,7 @@ const Die = struct { }; } - fn getAttrSecOffset(self: &const Die, id: u64) %u64 { + fn getAttrSecOffset(self: &const Die, id: u64) !u64 { const form_value = self.getAttr(id) ?? return error.MissingDebugInfo; return switch (*form_value) { FormValue.Const => |value| value.asUnsignedLe(), @@ -431,7 +419,7 @@ const Die = struct { }; } - fn getAttrUnsignedLe(self: &const Die, id: u64) %u64 { + fn getAttrUnsignedLe(self: &const Die, id: u64) !u64 { const form_value = self.getAttr(id) ?? return error.MissingDebugInfo; return switch (*form_value) { FormValue.Const => |value| value.asUnsignedLe(), @@ -439,7 +427,7 @@ const Die = struct { }; } - fn getAttrString(self: &const Die, st: &ElfStackTrace, id: u64) %[]u8 { + fn getAttrString(self: &const Die, st: &ElfStackTrace, id: u64) ![]u8 { const form_value = self.getAttr(id) ?? return error.MissingDebugInfo; return switch (*form_value) { FormValue.String => |value| value, @@ -512,7 +500,7 @@ const LineNumberProgram = struct { }; } - pub fn checkLineMatch(self: &LineNumberProgram) %?LineInfo { + pub fn checkLineMatch(self: &LineNumberProgram) !?LineInfo { if (self.target_address >= self.prev_address and self.target_address < self.address) { const file_entry = if (self.prev_file == 0) { return error.MissingDebugInfo; @@ -544,7 +532,7 @@ const LineNumberProgram = struct { } }; -fn readStringRaw(allocator: &mem.Allocator, in_stream: &io.InStream) %[]u8 { +fn readStringRaw(allocator: &mem.Allocator, in_stream: var) ![]u8 { var buf = ArrayList(u8).init(allocator); while (true) { const byte = try in_stream.readByte(); @@ -555,58 +543,70 @@ fn readStringRaw(allocator: &mem.Allocator, in_stream: &io.InStream) %[]u8 { return buf.toSlice(); } -fn getString(st: &ElfStackTrace, offset: u64) %[]u8 { +fn getString(st: &ElfStackTrace, offset: u64) ![]u8 { const pos = st.debug_str.offset + offset; try st.self_exe_file.seekTo(pos); return st.readString(); } -fn readAllocBytes(allocator: &mem.Allocator, in_stream: &io.InStream, size: usize) %[]u8 { +fn readAllocBytes(allocator: &mem.Allocator, in_stream: var, size: usize) ![]u8 { const buf = try global_allocator.alloc(u8, size); errdefer global_allocator.free(buf); if ((try in_stream.read(buf)) < size) return error.EndOfFile; return buf; } -fn parseFormValueBlockLen(allocator: &mem.Allocator, in_stream: &io.InStream, size: usize) %FormValue { +fn parseFormValueBlockLen(allocator: &mem.Allocator, in_stream: var, size: usize) !FormValue { const buf = try readAllocBytes(allocator, in_stream, size); return FormValue { .Block = buf }; } -fn parseFormValueBlock(allocator: &mem.Allocator, in_stream: &io.InStream, size: usize) %FormValue { +fn parseFormValueBlock(allocator: &mem.Allocator, in_stream: var, size: usize) !FormValue { const block_len = try in_stream.readVarInt(builtin.Endian.Little, usize, size); return parseFormValueBlockLen(allocator, in_stream, block_len); } -fn parseFormValueConstant(allocator: &mem.Allocator, in_stream: &io.InStream, signed: bool, size: usize) %FormValue { +fn parseFormValueConstant(allocator: &mem.Allocator, in_stream: var, signed: bool, size: usize) !FormValue { return FormValue { .Const = Constant { .signed = signed, .payload = try readAllocBytes(allocator, in_stream, size), }}; } -fn parseFormValueDwarfOffsetSize(in_stream: &io.InStream, is_64: bool) %u64 { +fn parseFormValueDwarfOffsetSize(in_stream: var, is_64: bool) !u64 { return if (is_64) try in_stream.readIntLe(u64) else u64(try in_stream.readIntLe(u32)) ; } -fn parseFormValueTargetAddrSize(in_stream: &io.InStream) %u64 { +fn parseFormValueTargetAddrSize(in_stream: var) !u64 { return if (@sizeOf(usize) == 4) u64(try in_stream.readIntLe(u32)) else if (@sizeOf(usize) == 8) try in_stream.readIntLe(u64) else unreachable; } -fn parseFormValueRefLen(allocator: &mem.Allocator, in_stream: &io.InStream, size: usize) %FormValue { +fn parseFormValueRefLen(allocator: &mem.Allocator, in_stream: var, size: usize) !FormValue { const buf = try readAllocBytes(allocator, in_stream, size); return FormValue { .Ref = buf }; } -fn parseFormValueRef(allocator: &mem.Allocator, in_stream: &io.InStream, comptime T: type) %FormValue { +fn parseFormValueRef(allocator: &mem.Allocator, in_stream: var, comptime T: type) !FormValue { const block_len = try in_stream.readIntLe(T); return parseFormValueRefLen(allocator, in_stream, block_len); } -fn parseFormValue(allocator: &mem.Allocator, in_stream: &io.InStream, form_id: u64, is_64: bool) %FormValue { +const ParseFormValueError = error { + EndOfStream, + Io, + BadFd, + Unexpected, + InvalidDebugInfo, + EndOfFile, + OutOfMemory, +}; + +fn parseFormValue(allocator: &mem.Allocator, in_stream: var, form_id: u64, is_64: bool) + ParseFormValueError!FormValue +{ return switch (form_id) { DW.FORM_addr => FormValue { .Address = try parseFormValueTargetAddrSize(in_stream) }, DW.FORM_block1 => parseFormValueBlock(allocator, in_stream, 1), @@ -656,7 +656,7 @@ fn parseFormValue(allocator: &mem.Allocator, in_stream: &io.InStream, form_id: u }; } -fn parseAbbrevTable(st: &ElfStackTrace) %AbbrevTable { +fn parseAbbrevTable(st: &ElfStackTrace) !AbbrevTable { const in_file = &st.self_exe_file; var in_file_stream = io.FileInStream.init(in_file); const in_stream = &in_file_stream.stream; @@ -688,7 +688,7 @@ fn parseAbbrevTable(st: &ElfStackTrace) %AbbrevTable { /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, /// seeks in the stream and parses it. -fn getAbbrevTable(st: &ElfStackTrace, abbrev_offset: u64) %&const AbbrevTable { +fn getAbbrevTable(st: &ElfStackTrace, abbrev_offset: u64) !&const AbbrevTable { for (st.abbrev_table_list.toSlice()) |*header| { if (header.offset == abbrev_offset) { return &header.table; @@ -710,7 +710,7 @@ fn getAbbrevTableEntry(abbrev_table: &const AbbrevTable, abbrev_code: u64) ?&con return null; } -fn parseDie(st: &ElfStackTrace, abbrev_table: &const AbbrevTable, is_64: bool) %Die { +fn parseDie(st: &ElfStackTrace, abbrev_table: &const AbbrevTable, is_64: bool) !Die { const in_file = &st.self_exe_file; var in_file_stream = io.FileInStream.init(in_file); const in_stream = &in_file_stream.stream; @@ -732,7 +732,7 @@ fn parseDie(st: &ElfStackTrace, abbrev_table: &const AbbrevTable, is_64: bool) % return result; } -fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, target_address: usize) %LineInfo { +fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, target_address: usize) !LineInfo { const compile_unit_cwd = try compile_unit.die.getAttrString(st, DW.AT_comp_dir); const in_file = &st.self_exe_file; @@ -747,7 +747,7 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe try in_file.seekTo(this_offset); var is_64: bool = undefined; - const unit_length = try readInitialLength(in_stream, &is_64); + const unit_length = try readInitialLength(@typeOf(in_stream.readFn).ReturnType.ErrorSet, in_stream, &is_64); if (unit_length == 0) return error.MissingDebugInfo; const next_offset = unit_length + (if (is_64) usize(12) else usize(4)); @@ -910,7 +910,7 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe return error.MissingDebugInfo; } -fn scanAllCompileUnits(st: &ElfStackTrace) %void { +fn scanAllCompileUnits(st: &ElfStackTrace) !void { const debug_info_end = st.debug_info.offset + st.debug_info.size; var this_unit_offset = st.debug_info.offset; var cu_index: usize = 0; @@ -922,7 +922,7 @@ fn scanAllCompileUnits(st: &ElfStackTrace) %void { try st.self_exe_file.seekTo(this_unit_offset); var is_64: bool = undefined; - const unit_length = try readInitialLength(in_stream, &is_64); + const unit_length = try readInitialLength(@typeOf(in_stream.readFn).ReturnType.ErrorSet, in_stream, &is_64); if (unit_length == 0) return; const next_offset = unit_length + (if (is_64) usize(12) else usize(4)); @@ -986,7 +986,7 @@ fn scanAllCompileUnits(st: &ElfStackTrace) %void { } } -fn findCompileUnit(st: &ElfStackTrace, target_address: u64) %&const CompileUnit { +fn findCompileUnit(st: &ElfStackTrace, target_address: u64) !&const CompileUnit { var in_file_stream = io.FileInStream.init(&st.self_exe_file); const in_stream = &in_file_stream.stream; for (st.compile_unit_list.toSlice()) |*compile_unit| { @@ -1022,7 +1022,7 @@ fn findCompileUnit(st: &ElfStackTrace, target_address: u64) %&const CompileUnit return error.MissingDebugInfo; } -fn readInitialLength(in_stream: &io.InStream, is_64: &bool) %u64 { +fn readInitialLength(comptime E: type, in_stream: &io.InStream(E), is_64: &bool) !u64 { const first_32_bits = try in_stream.readIntLe(u32); *is_64 = (first_32_bits == 0xffffffff); if (*is_64) { @@ -1033,7 +1033,7 @@ fn readInitialLength(in_stream: &io.InStream, is_64: &bool) %u64 { } } -fn readULeb128(in_stream: &io.InStream) %u64 { +fn readULeb128(in_stream: var) !u64 { var result: u64 = 0; var shift: usize = 0; @@ -1054,7 +1054,7 @@ fn readULeb128(in_stream: &io.InStream) %u64 { } } -fn readILeb128(in_stream: &io.InStream) %i64 { +fn readILeb128(in_stream: var) !i64 { var result: i64 = 0; var shift: usize = 0; diff --git a/std/elf.zig b/std/elf.zig index 59e2150c69..7e20fa000f 100644 --- a/std/elf.zig +++ b/std/elf.zig @@ -1,13 +1,12 @@ const builtin = @import("builtin"); const std = @import("index.zig"); const io = std.io; +const os = std.os; const math = std.math; const mem = std.mem; const debug = std.debug; const InStream = std.stream.InStream; -error InvalidFormat; - pub const SHT_NULL = 0; pub const SHT_PROGBITS = 1; pub const SHT_SYMTAB = 2; @@ -65,7 +64,7 @@ pub const SectionHeader = struct { }; pub const Elf = struct { - in_file: &io.File, + in_file: &os.File, auto_close_stream: bool, is_64: bool, endian: builtin.Endian, @@ -78,17 +77,17 @@ pub const Elf = struct { string_section: &SectionHeader, section_headers: []SectionHeader, allocator: &mem.Allocator, - prealloc_file: io.File, + prealloc_file: os.File, /// Call close when done. - pub fn openPath(elf: &Elf, allocator: &mem.Allocator, path: []const u8) %void { + pub fn openPath(elf: &Elf, allocator: &mem.Allocator, path: []const u8) !void { try elf.prealloc_file.open(path); try elf.openFile(allocator, &elf.prealloc_file); elf.auto_close_stream = true; } /// Call close when done. - pub fn openFile(elf: &Elf, allocator: &mem.Allocator, file: &io.File) %void { + pub fn openFile(elf: &Elf, allocator: &mem.Allocator, file: &os.File) !void { elf.allocator = allocator; elf.in_file = file; elf.auto_close_stream = false; @@ -239,7 +238,7 @@ pub const Elf = struct { elf.in_file.close(); } - pub fn findSection(elf: &Elf, name: []const u8) %?&SectionHeader { + pub fn findSection(elf: &Elf, name: []const u8) !?&SectionHeader { var file_stream = io.FileInStream.init(elf.in_file); const in = &file_stream.stream; @@ -263,7 +262,7 @@ pub const Elf = struct { return null; } - pub fn seekToSection(elf: &Elf, elf_section: &SectionHeader) %void { + pub fn seekToSection(elf: &Elf, elf_section: &SectionHeader) !void { try elf.in_file.seekTo(elf_section.offset); } }; diff --git a/std/fmt/index.zig b/std/fmt/index.zig index a32a6e0295..56b0add86d 100644 --- a/std/fmt/index.zig +++ b/std/fmt/index.zig @@ -24,8 +24,8 @@ const State = enum { // TODO put inside format function and make sure the name a /// Renders fmt string with args, calling output with slices of bytes. /// If `output` returns an error, the error is returned from `format` and /// `output` is not called again. -pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void, - comptime fmt: []const u8, args: ...) %void +pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8) Errors!void, + comptime fmt: []const u8, args: ...) Errors!void { comptime var start_index = 0; comptime var state = State.Start; @@ -58,7 +58,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void, start_index = i; }, '}' => { - try formatValue(args[next_arg], context, output); + try formatValue(args[next_arg], context, Errors, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -110,7 +110,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void, }, State.Integer => switch (c) { '}' => { - try formatInt(args[next_arg], radix, uppercase, width, context, output); + try formatInt(args[next_arg], radix, uppercase, width, context, Errors, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -124,7 +124,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void, State.IntegerWidth => switch (c) { '}' => { width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable); - try formatInt(args[next_arg], radix, uppercase, width, context, output); + try formatInt(args[next_arg], radix, uppercase, width, context, Errors, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -134,7 +134,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void, }, State.Float => switch (c) { '}' => { - try formatFloatDecimal(args[next_arg], 0, context, output); + try formatFloatDecimal(args[next_arg], 0, context, Errors, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -148,7 +148,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void, State.FloatWidth => switch (c) { '}' => { width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable); - try formatFloatDecimal(args[next_arg], width, context, output); + try formatFloatDecimal(args[next_arg], width, context, Errors, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -159,7 +159,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void, State.BufWidth => switch (c) { '}' => { width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable); - try formatBuf(args[next_arg], width, context, output); + try formatBuf(args[next_arg], width, context, Errors, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -169,7 +169,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void, }, State.Character => switch (c) { '}' => { - try formatAsciiChar(args[next_arg], context, output); + try formatAsciiChar(args[next_arg], context, Errors, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -191,14 +191,14 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void, } } -pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []const u8)%void) %void { +pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void { const T = @typeOf(value); switch (@typeId(T)) { builtin.TypeId.Int => { - return formatInt(value, 10, false, 0, context, output); + return formatInt(value, 10, false, 0, context, Errors, output); }, builtin.TypeId.Float => { - return formatFloat(value, context, output); + return formatFloat(value, context, Errors, output); }, builtin.TypeId.Void => { return output(context, "void"); @@ -208,19 +208,19 @@ pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []cons }, builtin.TypeId.Nullable => { if (value) |payload| { - return formatValue(payload, context, output); + return formatValue(payload, context, Errors, output); } else { return output(context, "null"); } }, builtin.TypeId.ErrorUnion => { if (value) |payload| { - return formatValue(payload, context, output); + return formatValue(payload, context, Errors, output); } else |err| { - return formatValue(err, context, output); + return formatValue(err, context, Errors, output); } }, - builtin.TypeId.Error => { + builtin.TypeId.ErrorSet => { try output(context, "error."); return output(context, @errorName(value)); }, @@ -228,7 +228,7 @@ pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []cons if (@typeId(T.Child) == builtin.TypeId.Array and T.Child.Child == u8) { return output(context, (*value)[0..]); } else { - return format(context, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); + return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); } }, else => if (@canImplicitCast([]const u8, value)) { @@ -240,12 +240,12 @@ pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []cons } } -pub fn formatAsciiChar(c: u8, context: var, output: fn(@typeOf(context), []const u8)%void) %void { +pub fn formatAsciiChar(c: u8, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void { return output(context, (&c)[0..1]); } pub fn formatBuf(buf: []const u8, width: usize, - context: var, output: fn(@typeOf(context), []const u8)%void) %void + context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void { try output(context, buf); @@ -256,7 +256,7 @@ pub fn formatBuf(buf: []const u8, width: usize, } } -pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []const u8)%void) %void { +pub fn formatFloat(value: var, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void { var x = f64(value); // Errol doesn't handle these special cases. @@ -290,11 +290,11 @@ pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []cons if (float_decimal.exp != 1) { try output(context, "e"); - try formatInt(float_decimal.exp - 1, 10, false, 0, context, output); + try formatInt(float_decimal.exp - 1, 10, false, 0, context, Errors, output); } } -pub fn formatFloatDecimal(value: var, precision: usize, context: var, output: fn(@typeOf(context), []const u8)%void) %void { +pub fn formatFloatDecimal(value: var, precision: usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void { var x = f64(value); // Errol doesn't handle these special cases. @@ -336,17 +336,17 @@ pub fn formatFloatDecimal(value: var, precision: usize, context: var, output: fn pub fn formatInt(value: var, base: u8, uppercase: bool, width: usize, - context: var, output: fn(@typeOf(context), []const u8)%void) %void + context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void { if (@typeOf(value).is_signed) { - return formatIntSigned(value, base, uppercase, width, context, output); + return formatIntSigned(value, base, uppercase, width, context, Errors, output); } else { - return formatIntUnsigned(value, base, uppercase, width, context, output); + return formatIntUnsigned(value, base, uppercase, width, context, Errors, output); } } fn formatIntSigned(value: var, base: u8, uppercase: bool, width: usize, - context: var, output: fn(@typeOf(context), []const u8)%void) %void + context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void { const uint = @IntType(false, @typeOf(value).bit_count); if (value < 0) { @@ -354,20 +354,20 @@ fn formatIntSigned(value: var, base: u8, uppercase: bool, width: usize, try output(context, (&minus_sign)[0..1]); const new_value = uint(-(value + 1)) + 1; const new_width = if (width == 0) 0 else (width - 1); - return formatIntUnsigned(new_value, base, uppercase, new_width, context, output); + return formatIntUnsigned(new_value, base, uppercase, new_width, context, Errors, output); } else if (width == 0) { - return formatIntUnsigned(uint(value), base, uppercase, width, context, output); + return formatIntUnsigned(uint(value), base, uppercase, width, context, Errors, output); } else { const plus_sign: u8 = '+'; try output(context, (&plus_sign)[0..1]); const new_value = uint(value); const new_width = if (width == 0) 0 else (width - 1); - return formatIntUnsigned(new_value, base, uppercase, new_width, context, output); + return formatIntUnsigned(new_value, base, uppercase, new_width, context, Errors, output); } } fn formatIntUnsigned(value: var, base: u8, uppercase: bool, width: usize, - context: var, output: fn(@typeOf(context), []const u8)%void) %void + context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void { // max_int_digits accounts for the minus sign. when printing an unsigned // number we don't need to do that. @@ -410,19 +410,19 @@ pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, width: .out_buf = out_buf, .index = 0, }; - formatInt(value, base, uppercase, width, &context, formatIntCallback) catch unreachable; + formatInt(value, base, uppercase, width, &context, error{}, formatIntCallback) catch unreachable; return context.index; } const FormatIntBuf = struct { out_buf: []u8, index: usize, }; -fn formatIntCallback(context: &FormatIntBuf, bytes: []const u8) %void { +fn formatIntCallback(context: &FormatIntBuf, bytes: []const u8) (error{}!void) { mem.copy(u8, context.out_buf[context.index..], bytes); context.index += bytes.len; } -pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) %T { +pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) !T { if (!T.is_signed) return parseUnsigned(T, buf, radix); if (buf.len == 0) @@ -439,14 +439,21 @@ pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) %T { test "fmt.parseInt" { assert((parseInt(i32, "-10", 10) catch unreachable) == -10); assert((parseInt(i32, "+10", 10) catch unreachable) == 10); - assert(if (parseInt(i32, " 10", 10)) |_| false else |err| err == error.InvalidChar); - assert(if (parseInt(i32, "10 ", 10)) |_| false else |err| err == error.InvalidChar); - assert(if (parseInt(u32, "-10", 10)) |_| false else |err| err == error.InvalidChar); + assert(if (parseInt(i32, " 10", 10)) |_| false else |err| err == error.InvalidCharacter); + assert(if (parseInt(i32, "10 ", 10)) |_| false else |err| err == error.InvalidCharacter); + assert(if (parseInt(u32, "-10", 10)) |_| false else |err| err == error.InvalidCharacter); assert((parseInt(u8, "255", 10) catch unreachable) == 255); assert(if (parseInt(u8, "256", 10)) |_| false else |err| err == error.Overflow); } -pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) %T { +const ParseUnsignedError = error { + /// The result cannot fit in the type specified + Overflow, + /// The input had a byte that was not a digit + InvalidCharacter, +}; + +pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) ParseUnsignedError!T { var x: T = 0; for (buf) |c| { @@ -458,17 +465,16 @@ pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) %T { return x; } -error InvalidChar; -fn charToDigit(c: u8, radix: u8) %u8 { +fn charToDigit(c: u8, radix: u8) (error{InvalidCharacter}!u8) { const value = switch (c) { '0' ... '9' => c - '0', 'A' ... 'Z' => c - 'A' + 10, 'a' ... 'z' => c - 'a' + 10, - else => return error.InvalidChar, + else => return error.InvalidCharacter, }; if (value >= radix) - return error.InvalidChar; + return error.InvalidCharacter; return value; } @@ -485,28 +491,26 @@ const BufPrintContext = struct { remaining: []u8, }; -error BufferTooSmall; -fn bufPrintWrite(context: &BufPrintContext, bytes: []const u8) %void { +fn bufPrintWrite(context: &BufPrintContext, bytes: []const u8) !void { if (context.remaining.len < bytes.len) return error.BufferTooSmall; mem.copy(u8, context.remaining, bytes); context.remaining = context.remaining[bytes.len..]; } -pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) %[]u8 { +pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) ![]u8 { var context = BufPrintContext { .remaining = buf, }; - try format(&context, bufPrintWrite, fmt, args); + try format(&context, error{BufferTooSmall}, bufPrintWrite, fmt, args); return buf[0..buf.len - context.remaining.len]; } -pub fn allocPrint(allocator: &mem.Allocator, comptime fmt: []const u8, args: ...) %[]u8 { +pub fn allocPrint(allocator: &mem.Allocator, comptime fmt: []const u8, args: ...) ![]u8 { var size: usize = 0; - // Cannot fail because `countSize` cannot fail. - format(&size, countSize, fmt, args) catch unreachable; + format(&size, error{}, countSize, fmt, args) catch |err| switch (err) {}; const buf = try allocator.alloc(u8, size); return bufPrint(buf, fmt, args); } -fn countSize(size: &usize, bytes: []const u8) %void { +fn countSize(size: &usize, bytes: []const u8) (error{}!void) { *size += bytes.len; } @@ -534,7 +538,7 @@ fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, width: u test "parse u64 digit too big" { _ = parseUnsigned(u64, "123a", 10) catch |err| { - if (err == error.InvalidChar) return; + if (err == error.InvalidCharacter) return; unreachable; }; unreachable; @@ -567,13 +571,13 @@ test "fmt.format" { } { var buf1: [32]u8 = undefined; - const value: %i32 = 1234; + const value: error!i32 = 1234; const result = try bufPrint(buf1[0..], "error union: {}\n", value); assert(mem.eql(u8, result, "error union: 1234\n")); } { var buf1: [32]u8 = undefined; - const value: %i32 = error.InvalidChar; + const value: error!i32 = error.InvalidChar; const result = try bufPrint(buf1[0..], "error union: {}\n", value); assert(mem.eql(u8, result, "error union: error.InvalidChar\n")); } diff --git a/std/hash_map.zig b/std/hash_map.zig index 96ec10b933..659783bc84 100644 --- a/std/hash_map.zig +++ b/std/hash_map.zig @@ -80,7 +80,7 @@ pub fn HashMap(comptime K: type, comptime V: type, } /// Returns the value that was already there. - pub fn put(hm: &Self, key: K, value: &const V) %?V { + pub fn put(hm: &Self, key: K, value: &const V) !?V { if (hm.entries.len == 0) { try hm.initCapacity(16); } @@ -151,7 +151,7 @@ pub fn HashMap(comptime K: type, comptime V: type, }; } - fn initCapacity(hm: &Self, capacity: usize) %void { + fn initCapacity(hm: &Self, capacity: usize) !void { hm.entries = try hm.allocator.alloc(Entry, capacity); hm.size = 0; hm.max_distance_from_start_index = 0; diff --git a/std/heap.zig b/std/heap.zig index f023e7376d..2ff0e665c9 100644 --- a/std/heap.zig +++ b/std/heap.zig @@ -9,8 +9,6 @@ const c = std.c; const Allocator = mem.Allocator; -error OutOfMemory; - pub const c_allocator = &c_allocator_state; var c_allocator_state = Allocator { .allocFn = cAlloc, @@ -18,14 +16,14 @@ var c_allocator_state = Allocator { .freeFn = cFree, }; -fn cAlloc(self: &Allocator, n: usize, alignment: u29) %[]u8 { +fn cAlloc(self: &Allocator, n: usize, alignment: u29) ![]u8 { return if (c.malloc(usize(n))) |buf| @ptrCast(&u8, buf)[0..n] else error.OutOfMemory; } -fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) %[]u8 { +fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 { const old_ptr = @ptrCast(&c_void, old_mem.ptr); if (c.realloc(old_ptr, new_size)) |buf| { return @ptrCast(&u8, buf)[0..new_size]; @@ -47,7 +45,7 @@ pub const IncrementingAllocator = struct { end_index: usize, heap_handle: if (builtin.os == Os.windows) os.windows.HANDLE else void, - fn init(capacity: usize) %IncrementingAllocator { + fn init(capacity: usize) !IncrementingAllocator { switch (builtin.os) { Os.linux, Os.macosx, Os.ios => { const p = os.posix; @@ -105,7 +103,7 @@ pub const IncrementingAllocator = struct { return self.bytes.len - self.end_index; } - fn alloc(allocator: &Allocator, n: usize, alignment: u29) %[]u8 { + fn alloc(allocator: &Allocator, n: usize, alignment: u29) ![]u8 { const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator); const addr = @ptrToInt(&self.bytes[self.end_index]); const rem = @rem(addr, alignment); @@ -120,7 +118,7 @@ pub const IncrementingAllocator = struct { return result; } - fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) %[]u8 { + fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 { if (new_size <= old_mem.len) { return old_mem[0..new_size]; } else { diff --git a/std/index.zig b/std/index.zig index b5a80cba23..8d292c2f5c 100644 --- a/std/index.zig +++ b/std/index.zig @@ -28,6 +28,7 @@ pub const os = @import("os/index.zig"); pub const rand = @import("rand.zig"); pub const sort = @import("sort.zig"); pub const unicode = @import("unicode.zig"); +pub const zig = @import("zig/index.zig"); test "std" { // run tests from these @@ -58,4 +59,5 @@ test "std" { _ = @import("rand.zig"); _ = @import("sort.zig"); _ = @import("unicode.zig"); + _ = @import("zig/index.zig"); } diff --git a/std/io.zig b/std/io.zig index 2fe57e4dfe..94685c4d03 100644 --- a/std/io.zig +++ b/std/io.zig @@ -1,12 +1,6 @@ const std = @import("index.zig"); const builtin = @import("builtin"); const Os = builtin.Os; -const system = switch(builtin.os) { - Os.linux => @import("os/linux/index.zig"), - Os.macosx, Os.ios => @import("os/darwin.zig"), - Os.windows => @import("os/windows/index.zig"), - else => @compileError("Unsupported OS"), -}; const c = std.c; const math = std.math; @@ -16,65 +10,38 @@ const os = std.os; const mem = std.mem; const Buffer = std.Buffer; const fmt = std.fmt; +const File = std.os.File; const is_posix = builtin.os != builtin.Os.windows; const is_windows = builtin.os == builtin.Os.windows; -test "import io tests" { - comptime { - _ = @import("io_test.zig"); - } -} +const GetStdIoErrs = os.WindowsGetStdHandleErrs; -/// The function received invalid input at runtime. An Invalid error means a -/// bug in the program that called the function. -error Invalid; - -error DiskQuota; -error FileTooBig; -error Io; -error NoSpaceLeft; -error BadPerm; -error BrokenPipe; -error BadFd; -error IsDir; -error NotDir; -error SymLinkLoop; -error ProcessFdQuotaExceeded; -error SystemFdQuotaExceeded; -error NameTooLong; -error NoDevice; -error PathNotFound; -error OutOfMemory; -error Unseekable; -error EndOfFile; -error FilePosLargerThanPointerRange; - -pub fn getStdErr() %File { +pub fn getStdErr() GetStdIoErrs!File { const handle = if (is_windows) - try os.windowsGetStdHandle(system.STD_ERROR_HANDLE) + try os.windowsGetStdHandle(os.windows.STD_ERROR_HANDLE) else if (is_posix) - system.STDERR_FILENO + os.posix.STDERR_FILENO else unreachable; return File.openHandle(handle); } -pub fn getStdOut() %File { +pub fn getStdOut() GetStdIoErrs!File { const handle = if (is_windows) - try os.windowsGetStdHandle(system.STD_OUTPUT_HANDLE) + try os.windowsGetStdHandle(os.windows.STD_OUTPUT_HANDLE) else if (is_posix) - system.STDOUT_FILENO + os.posix.STDOUT_FILENO else unreachable; return File.openHandle(handle); } -pub fn getStdIn() %File { +pub fn getStdIn() GetStdIoErrs!File { const handle = if (is_windows) - try os.windowsGetStdHandle(system.STD_INPUT_HANDLE) + try os.windowsGetStdHandle(os.windows.STD_INPUT_HANDLE) else if (is_posix) - system.STDIN_FILENO + os.posix.STDIN_FILENO else unreachable; return File.openHandle(handle); @@ -83,18 +50,21 @@ pub fn getStdIn() %File { /// Implementation of InStream trait for File pub const FileInStream = struct { file: &File, - stream: InStream, + stream: Stream, + + pub const Error = @typeOf(File.read).ReturnType.ErrorSet; + pub const Stream = InStream(Error); pub fn init(file: &File) FileInStream { return FileInStream { .file = file, - .stream = InStream { + .stream = Stream { .readFn = readFn, }, }; } - fn readFn(in_stream: &InStream, buffer: []u8) %usize { + fn readFn(in_stream: &Stream, buffer: []u8) Error!usize { const self = @fieldParentPtr(FileInStream, "stream", in_stream); return self.file.read(buffer); } @@ -103,453 +73,202 @@ pub const FileInStream = struct { /// Implementation of OutStream trait for File pub const FileOutStream = struct { file: &File, - stream: OutStream, + stream: Stream, + + pub const Error = File.WriteError; + pub const Stream = OutStream(Error); pub fn init(file: &File) FileOutStream { return FileOutStream { .file = file, - .stream = OutStream { + .stream = Stream { .writeFn = writeFn, }, }; } - fn writeFn(out_stream: &OutStream, bytes: []const u8) %void { + fn writeFn(out_stream: &Stream, bytes: []const u8) !void { const self = @fieldParentPtr(FileOutStream, "stream", out_stream); return self.file.write(bytes); } }; -pub const File = struct { - /// The OS-specific file descriptor or file handle. - handle: os.FileHandle, +pub fn InStream(comptime ReadError: type) type { + return struct { + const Self = this; + pub const Error = ReadError; - /// `path` may need to be copied in memory to add a null terminating byte. In this case - /// a fixed size buffer of size std.os.max_noalloc_path_len is an attempted solution. If the fixed - /// size buffer is too small, and the provided allocator is null, error.NameTooLong is returned. - /// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory. - /// Call close to clean up. - pub fn openRead(path: []const u8, allocator: ?&mem.Allocator) %File { - if (is_posix) { - const flags = system.O_LARGEFILE|system.O_RDONLY; - const fd = try os.posixOpen(path, flags, 0, allocator); - return openHandle(fd); - } else if (is_windows) { - const handle = try os.windowsOpen(path, system.GENERIC_READ, system.FILE_SHARE_READ, - system.OPEN_EXISTING, system.FILE_ATTRIBUTE_NORMAL, allocator); - return openHandle(handle); - } else { - unreachable; - } - } + /// Return the number of bytes read. If the number read is smaller than buf.len, it + /// means the stream reached the end. Reaching the end of a stream is not an error + /// condition. + readFn: fn(self: &Self, buffer: []u8) Error!usize, - /// Calls `openWriteMode` with 0o666 for the mode. - pub fn openWrite(path: []const u8, allocator: ?&mem.Allocator) %File { - return openWriteMode(path, 0o666, allocator); + /// Replaces `buffer` contents by reading from the stream until it is finished. + /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and + /// the contents read from the stream are lost. + pub fn readAllBuffer(self: &Self, buffer: &Buffer, max_size: usize) !void { + try buffer.resize(0); - } + var actual_buf_len: usize = 0; + while (true) { + const dest_slice = buffer.toSlice()[actual_buf_len..]; + const bytes_read = try self.readFn(self, dest_slice); + actual_buf_len += bytes_read; - /// `path` may need to be copied in memory to add a null terminating byte. In this case - /// a fixed size buffer of size std.os.max_noalloc_path_len is an attempted solution. If the fixed - /// size buffer is too small, and the provided allocator is null, error.NameTooLong is returned. - /// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory. - /// Call close to clean up. - pub fn openWriteMode(path: []const u8, mode: usize, allocator: ?&mem.Allocator) %File { - if (is_posix) { - const flags = system.O_LARGEFILE|system.O_WRONLY|system.O_CREAT|system.O_CLOEXEC|system.O_TRUNC; - const fd = try os.posixOpen(path, flags, mode, allocator); - return openHandle(fd); - } else if (is_windows) { - const handle = try os.windowsOpen(path, system.GENERIC_WRITE, - system.FILE_SHARE_WRITE|system.FILE_SHARE_READ|system.FILE_SHARE_DELETE, - system.CREATE_ALWAYS, system.FILE_ATTRIBUTE_NORMAL, allocator); - return openHandle(handle); - } else { - unreachable; - } - - } - - pub fn openHandle(handle: os.FileHandle) File { - return File { - .handle = handle, - }; - } - - - /// Upon success, the stream is in an uninitialized state. To continue using it, - /// you must use the open() function. - pub fn close(self: &File) void { - os.close(self.handle); - self.handle = undefined; - } - - /// Calls `os.isTty` on `self.handle`. - pub fn isTty(self: &File) bool { - return os.isTty(self.handle); - } - - pub fn seekForward(self: &File, amount: isize) %void { - switch (builtin.os) { - Os.linux, Os.macosx, Os.ios => { - const result = system.lseek(self.handle, amount, system.SEEK_CUR); - const err = system.getErrno(result); - if (err > 0) { - return switch (err) { - system.EBADF => error.BadFd, - system.EINVAL => error.Unseekable, - system.EOVERFLOW => error.Unseekable, - system.ESPIPE => error.Unseekable, - system.ENXIO => error.Unseekable, - else => os.unexpectedErrorPosix(err), - }; - } - }, - Os.windows => { - if (system.SetFilePointerEx(self.handle, amount, null, system.FILE_CURRENT) == 0) { - const err = system.GetLastError(); - return switch (err) { - system.ERROR.INVALID_PARAMETER => error.BadFd, - else => os.unexpectedErrorWindows(err), - }; - } - }, - else => @compileError("unsupported OS"), - } - } - - pub fn seekTo(self: &File, pos: usize) %void { - switch (builtin.os) { - Os.linux, Os.macosx, Os.ios => { - const ipos = try math.cast(isize, pos); - const result = system.lseek(self.handle, ipos, system.SEEK_SET); - const err = system.getErrno(result); - if (err > 0) { - return switch (err) { - system.EBADF => error.BadFd, - system.EINVAL => error.Unseekable, - system.EOVERFLOW => error.Unseekable, - system.ESPIPE => error.Unseekable, - system.ENXIO => error.Unseekable, - else => os.unexpectedErrorPosix(err), - }; - } - }, - Os.windows => { - const ipos = try math.cast(isize, pos); - if (system.SetFilePointerEx(self.handle, ipos, null, system.FILE_BEGIN) == 0) { - const err = system.GetLastError(); - return switch (err) { - system.ERROR.INVALID_PARAMETER => error.BadFd, - else => os.unexpectedErrorWindows(err), - }; - } - }, - else => @compileError("unsupported OS: " ++ @tagName(builtin.os)), - } - } - - pub fn getPos(self: &File) %usize { - switch (builtin.os) { - Os.linux, Os.macosx, Os.ios => { - const result = system.lseek(self.handle, 0, system.SEEK_CUR); - const err = system.getErrno(result); - if (err > 0) { - return switch (err) { - system.EBADF => error.BadFd, - system.EINVAL => error.Unseekable, - system.EOVERFLOW => error.Unseekable, - system.ESPIPE => error.Unseekable, - system.ENXIO => error.Unseekable, - else => os.unexpectedErrorPosix(err), - }; - } - return result; - }, - Os.windows => { - var pos : system.LARGE_INTEGER = undefined; - if (system.SetFilePointerEx(self.handle, 0, &pos, system.FILE_CURRENT) == 0) { - const err = system.GetLastError(); - return switch (err) { - system.ERROR.INVALID_PARAMETER => error.BadFd, - else => os.unexpectedErrorWindows(err), - }; + if (bytes_read != dest_slice.len) { + buffer.shrink(actual_buf_len); + return; } - assert(pos >= 0); - if (@sizeOf(@typeOf(pos)) > @sizeOf(usize)) { - if (pos > @maxValue(usize)) { - return error.FilePosLargerThanPointerRange; - } - } - - return usize(pos); - }, - else => @compileError("unsupported OS"), - } - } - - pub fn getEndPos(self: &File) %usize { - if (is_posix) { - var stat: system.Stat = undefined; - const err = system.getErrno(system.fstat(self.handle, &stat)); - if (err > 0) { - return switch (err) { - system.EBADF => error.BadFd, - system.ENOMEM => error.SystemResources, - else => os.unexpectedErrorPosix(err), - }; + const new_buf_size = math.min(max_size, actual_buf_len + os.page_size); + if (new_buf_size == actual_buf_len) + return error.StreamTooLong; + try buffer.resize(new_buf_size); } - - return usize(stat.size); - } else if (is_windows) { - var file_size: system.LARGE_INTEGER = undefined; - if (system.GetFileSizeEx(self.handle, &file_size) == 0) { - const err = system.GetLastError(); - return switch (err) { - else => os.unexpectedErrorWindows(err), - }; - } - if (file_size < 0) - return error.Overflow; - return math.cast(usize, u64(file_size)); - } else { - unreachable; } - } - pub fn read(self: &File, buffer: []u8) %usize { - if (is_posix) { - var index: usize = 0; - while (index < buffer.len) { - const amt_read = system.read(self.handle, &buffer[index], buffer.len - index); - const read_err = system.getErrno(amt_read); - if (read_err > 0) { - switch (read_err) { - system.EINTR => continue, - system.EINVAL => unreachable, - system.EFAULT => unreachable, - system.EBADF => return error.BadFd, - system.EIO => return error.Io, - else => return os.unexpectedErrorPosix(read_err), - } + /// Allocates enough memory to hold all the contents of the stream. If the allocated + /// memory would be greater than `max_size`, returns `error.StreamTooLong`. + /// Caller owns returned memory. + /// If this function returns an error, the contents from the stream read so far are lost. + pub fn readAllAlloc(self: &Self, allocator: &mem.Allocator, max_size: usize) ![]u8 { + var buf = Buffer.initNull(allocator); + defer buf.deinit(); + + try self.readAllBuffer(&buf, max_size); + return buf.toOwnedSlice(); + } + + /// Replaces `buffer` contents by reading from the stream until `delimiter` is found. + /// Does not include the delimiter in the result. + /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and the contents + /// read from the stream so far are lost. + pub fn readUntilDelimiterBuffer(self: &Self, buffer: &Buffer, delimiter: u8, max_size: usize) !void { + try buf.resize(0); + + while (true) { + var byte: u8 = try self.readByte(); + + if (byte == delimiter) { + return; } - if (amt_read == 0) return index; - index += amt_read; - } - return index; - } else if (is_windows) { - var index: usize = 0; - while (index < buffer.len) { - const want_read_count = system.DWORD(math.min(system.DWORD(@maxValue(system.DWORD)), buffer.len - index)); - var amt_read: system.DWORD = undefined; - if (system.ReadFile(self.handle, @ptrCast(&c_void, &buffer[index]), want_read_count, &amt_read, null) == 0) { - const err = system.GetLastError(); - return switch (err) { - system.ERROR.OPERATION_ABORTED => continue, - system.ERROR.BROKEN_PIPE => return index, - else => os.unexpectedErrorWindows(err), - }; + + if (buf.len() == max_size) { + return error.StreamTooLong; } - if (amt_read == 0) return index; - index += amt_read; + + try buf.appendByte(byte); } - return index; - } else { - unreachable; } - } - fn write(self: &File, bytes: []const u8) %void { - if (is_posix) { - try os.posixWrite(self.handle, bytes); - } else if (is_windows) { - try os.windowsWrite(self.handle, bytes); - } else { - @compileError("Unsupported OS"); + /// Allocates enough memory to read until `delimiter`. If the allocated + /// memory would be greater than `max_size`, returns `error.StreamTooLong`. + /// Caller owns returned memory. + /// If this function returns an error, the contents from the stream read so far are lost. + pub fn readUntilDelimiterAlloc(self: &Self, allocator: &mem.Allocator, + delimiter: u8, max_size: usize) ![]u8 + { + var buf = Buffer.initNull(allocator); + defer buf.deinit(); + + try self.readUntilDelimiterBuffer(self, &buf, delimiter, max_size); + return buf.toOwnedSlice(); } - } -}; -error StreamTooLong; -error EndOfStream; + /// Returns the number of bytes read. If the number read is smaller than buf.len, it + /// means the stream reached the end. Reaching the end of a stream is not an error + /// condition. + pub fn read(self: &Self, buffer: []u8) !usize { + return self.readFn(self, buffer); + } -pub const InStream = struct { - /// Return the number of bytes read. If the number read is smaller than buf.len, it - /// means the stream reached the end. Reaching the end of a stream is not an error - /// condition. - readFn: fn(self: &InStream, buffer: []u8) %usize, + /// Same as `read` but end of stream returns `error.EndOfStream`. + pub fn readNoEof(self: &Self, buf: []u8) !void { + const amt_read = try self.read(buf); + if (amt_read < buf.len) return error.EndOfStream; + } - /// Replaces `buffer` contents by reading from the stream until it is finished. - /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and - /// the contents read from the stream are lost. - pub fn readAllBuffer(self: &InStream, buffer: &Buffer, max_size: usize) %void { - try buffer.resize(0); + /// Reads 1 byte from the stream or returns `error.EndOfStream`. + pub fn readByte(self: &Self) !u8 { + var result: [1]u8 = undefined; + try self.readNoEof(result[0..]); + return result[0]; + } - var actual_buf_len: usize = 0; - while (true) { - const dest_slice = buffer.toSlice()[actual_buf_len..]; - const bytes_read = try self.readFn(self, dest_slice); - actual_buf_len += bytes_read; + /// Same as `readByte` except the returned byte is signed. + pub fn readByteSigned(self: &Self) !i8 { + return @bitCast(i8, try self.readByte()); + } - if (bytes_read != dest_slice.len) { - buffer.shrink(actual_buf_len); - return; + pub fn readIntLe(self: &Self, comptime T: type) !T { + return self.readInt(builtin.Endian.Little, T); + } + + pub fn readIntBe(self: &Self, comptime T: type) !T { + return self.readInt(builtin.Endian.Big, T); + } + + pub fn readInt(self: &Self, endian: builtin.Endian, comptime T: type) !T { + var bytes: [@sizeOf(T)]u8 = undefined; + try self.readNoEof(bytes[0..]); + return mem.readInt(bytes, T, endian); + } + + pub fn readVarInt(self: &Self, endian: builtin.Endian, comptime T: type, size: usize) !T { + assert(size <= @sizeOf(T)); + assert(size <= 8); + var input_buf: [8]u8 = undefined; + const input_slice = input_buf[0..size]; + try self.readNoEof(input_slice); + return mem.readInt(input_slice, T, endian); + } + }; +} + +pub fn OutStream(comptime WriteError: type) type { + return struct { + const Self = this; + pub const Error = WriteError; + + writeFn: fn(self: &Self, bytes: []const u8) Error!void, + + pub fn print(self: &Self, comptime format: []const u8, args: ...) !void { + return std.fmt.format(self, Error, self.writeFn, format, args); + } + + pub fn write(self: &Self, bytes: []const u8) !void { + return self.writeFn(self, bytes); + } + + pub fn writeByte(self: &Self, byte: u8) !void { + const slice = (&byte)[0..1]; + return self.writeFn(self, slice); + } + + pub fn writeByteNTimes(self: &Self, byte: u8, n: usize) !void { + const slice = (&byte)[0..1]; + var i: usize = 0; + while (i < n) : (i += 1) { + try self.writeFn(self, slice); } - - const new_buf_size = math.min(max_size, actual_buf_len + os.page_size); - if (new_buf_size == actual_buf_len) - return error.StreamTooLong; - try buffer.resize(new_buf_size); } - } + }; +} - /// Allocates enough memory to hold all the contents of the stream. If the allocated - /// memory would be greater than `max_size`, returns `error.StreamTooLong`. - /// Caller owns returned memory. - /// If this function returns an error, the contents from the stream read so far are lost. - pub fn readAllAlloc(self: &InStream, allocator: &mem.Allocator, max_size: usize) %[]u8 { - var buf = Buffer.initNull(allocator); - defer buf.deinit(); - - try self.readAllBuffer(&buf, max_size); - return buf.toOwnedSlice(); - } - - /// Replaces `buffer` contents by reading from the stream until `delimiter` is found. - /// Does not include the delimiter in the result. - /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and the contents - /// read from the stream so far are lost. - pub fn readUntilDelimiterBuffer(self: &InStream, buffer: &Buffer, delimiter: u8, max_size: usize) %void { - try buf.resize(0); - - while (true) { - var byte: u8 = try self.readByte(); - - if (byte == delimiter) { - return; - } - - if (buf.len() == max_size) { - return error.StreamTooLong; - } - - try buf.appendByte(byte); - } - } - - /// Allocates enough memory to read until `delimiter`. If the allocated - /// memory would be greater than `max_size`, returns `error.StreamTooLong`. - /// Caller owns returned memory. - /// If this function returns an error, the contents from the stream read so far are lost. - pub fn readUntilDelimiterAlloc(self: &InStream, allocator: &mem.Allocator, - delimiter: u8, max_size: usize) %[]u8 - { - var buf = Buffer.initNull(allocator); - defer buf.deinit(); - - try self.readUntilDelimiterBuffer(self, &buf, delimiter, max_size); - return buf.toOwnedSlice(); - } - - /// Returns the number of bytes read. If the number read is smaller than buf.len, it - /// means the stream reached the end. Reaching the end of a stream is not an error - /// condition. - pub fn read(self: &InStream, buffer: []u8) %usize { - return self.readFn(self, buffer); - } - - /// Same as `read` but end of stream returns `error.EndOfStream`. - pub fn readNoEof(self: &InStream, buf: []u8) %void { - const amt_read = try self.read(buf); - if (amt_read < buf.len) return error.EndOfStream; - } - - /// Reads 1 byte from the stream or returns `error.EndOfStream`. - pub fn readByte(self: &InStream) %u8 { - var result: [1]u8 = undefined; - try self.readNoEof(result[0..]); - return result[0]; - } - - /// Same as `readByte` except the returned byte is signed. - pub fn readByteSigned(self: &InStream) %i8 { - return @bitCast(i8, try self.readByte()); - } - - pub fn readIntLe(self: &InStream, comptime T: type) %T { - return self.readInt(builtin.Endian.Little, T); - } - - pub fn readIntBe(self: &InStream, comptime T: type) %T { - return self.readInt(builtin.Endian.Big, T); - } - - pub fn readInt(self: &InStream, endian: builtin.Endian, comptime T: type) %T { - var bytes: [@sizeOf(T)]u8 = undefined; - try self.readNoEof(bytes[0..]); - return mem.readInt(bytes, T, endian); - } - - pub fn readVarInt(self: &InStream, endian: builtin.Endian, comptime T: type, size: usize) %T { - assert(size <= @sizeOf(T)); - assert(size <= 8); - var input_buf: [8]u8 = undefined; - const input_slice = input_buf[0..size]; - try self.readNoEof(input_slice); - return mem.readInt(input_slice, T, endian); - } - - -}; - -pub const OutStream = struct { - writeFn: fn(self: &OutStream, bytes: []const u8) %void, - - pub fn print(self: &OutStream, comptime format: []const u8, args: ...) %void { - return std.fmt.format(self, self.writeFn, format, args); - } - - pub fn write(self: &OutStream, bytes: []const u8) %void { - return self.writeFn(self, bytes); - } - - pub fn writeByte(self: &OutStream, byte: u8) %void { - const slice = (&byte)[0..1]; - return self.writeFn(self, slice); - } - - pub fn writeByteNTimes(self: &OutStream, byte: u8, n: usize) %void { - const slice = (&byte)[0..1]; - var i: usize = 0; - while (i < n) : (i += 1) { - try self.writeFn(self, slice); - } - } -}; - -/// `path` may need to be copied in memory to add a null terminating byte. In this case -/// a fixed size buffer of size `std.os.max_noalloc_path_len` is an attempted solution. If the fixed -/// size buffer is too small, and the provided allocator is null, `error.NameTooLong` is returned. -/// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory. -pub fn writeFile(path: []const u8, data: []const u8, allocator: ?&mem.Allocator) %void { - var file = try File.openWrite(path, allocator); +/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. +pub fn writeFile(allocator: &mem.Allocator, path: []const u8, data: []const u8) !void { + var file = try File.openWrite(allocator, path); defer file.close(); try file.write(data); } /// On success, caller owns returned buffer. -pub fn readFileAlloc(path: []const u8, allocator: &mem.Allocator) %[]u8 { - return readFileAllocExtra(path, allocator, 0); -} -/// On success, caller owns returned buffer. -/// Allocates extra_len extra bytes at the end of the file buffer, which are uninitialized. -pub fn readFileAllocExtra(path: []const u8, allocator: &mem.Allocator, extra_len: usize) %[]u8 { - var file = try File.openRead(path, allocator); +pub fn readFileAlloc(allocator: &mem.Allocator, path: []const u8) ![]u8 { + var file = try File.openRead(allocator, path); defer file.close(); const size = try file.getEndPos(); - const buf = try allocator.alloc(u8, size + extra_len); + const buf = try allocator.alloc(u8, size); errdefer allocator.free(buf); var adapter = FileInStream.init(&file); @@ -557,21 +276,24 @@ pub fn readFileAllocExtra(path: []const u8, allocator: &mem.Allocator, extra_len return buf; } -pub const BufferedInStream = BufferedInStreamCustom(os.page_size); +pub fn BufferedInStream(comptime Error: type) type { + return BufferedInStreamCustom(os.page_size, Error); +} -pub fn BufferedInStreamCustom(comptime buffer_size: usize) type { +pub fn BufferedInStreamCustom(comptime buffer_size: usize, comptime Error: type) type { return struct { const Self = this; + const Stream = InStream(Error); - pub stream: InStream, + pub stream: Stream, - unbuffered_in_stream: &InStream, + unbuffered_in_stream: &Stream, buffer: [buffer_size]u8, start_index: usize, end_index: usize, - pub fn init(unbuffered_in_stream: &InStream) Self { + pub fn init(unbuffered_in_stream: &Stream) Self { return Self { .unbuffered_in_stream = unbuffered_in_stream, .buffer = undefined, @@ -583,13 +305,13 @@ pub fn BufferedInStreamCustom(comptime buffer_size: usize) type { .start_index = buffer_size, .end_index = buffer_size, - .stream = InStream { + .stream = Stream { .readFn = readFn, }, }; } - fn readFn(in_stream: &InStream, dest: []u8) %usize { + fn readFn(in_stream: &Stream, dest: []u8) !usize { const self = @fieldParentPtr(Self, "stream", in_stream); var dest_index: usize = 0; @@ -628,39 +350,40 @@ pub fn BufferedInStreamCustom(comptime buffer_size: usize) type { }; } -pub const BufferedOutStream = BufferedOutStreamCustom(os.page_size); +pub fn BufferedOutStream(comptime Error: type) type { + return BufferedOutStreamCustom(os.page_size, Error); +} -pub fn BufferedOutStreamCustom(comptime buffer_size: usize) type { +pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime OutStreamError: type) type { return struct { const Self = this; + pub const Stream = OutStream(Error); + pub const Error = OutStreamError; - pub stream: OutStream, + pub stream: Stream, - unbuffered_out_stream: &OutStream, + unbuffered_out_stream: &Stream, buffer: [buffer_size]u8, index: usize, - pub fn init(unbuffered_out_stream: &OutStream) Self { + pub fn init(unbuffered_out_stream: &Stream) Self { return Self { .unbuffered_out_stream = unbuffered_out_stream, .buffer = undefined, .index = 0, - .stream = OutStream { + .stream = Stream { .writeFn = writeFn, }, }; } - pub fn flush(self: &Self) %void { - if (self.index == 0) - return; - + pub fn flush(self: &Self) !void { try self.unbuffered_out_stream.write(self.buffer[0..self.index]); self.index = 0; } - fn writeFn(out_stream: &OutStream, bytes: []const u8) %void { + fn writeFn(out_stream: &Stream, bytes: []const u8) !void { const self = @fieldParentPtr(Self, "stream", out_stream); if (bytes.len >= self.buffer.len) { @@ -687,20 +410,71 @@ pub fn BufferedOutStreamCustom(comptime buffer_size: usize) type { /// Implementation of OutStream trait for Buffer pub const BufferOutStream = struct { buffer: &Buffer, - stream: OutStream, + stream: Stream, + + pub const Error = error{OutOfMemory}; + pub const Stream = OutStream(Error); pub fn init(buffer: &Buffer) BufferOutStream { return BufferOutStream { .buffer = buffer, - .stream = OutStream { + .stream = Stream { .writeFn = writeFn, }, }; } - fn writeFn(out_stream: &OutStream, bytes: []const u8) %void { + fn writeFn(out_stream: &Stream, bytes: []const u8) !void { const self = @fieldParentPtr(BufferOutStream, "stream", out_stream); return self.buffer.append(bytes); } }; + +pub const BufferedAtomicFile = struct { + atomic_file: os.AtomicFile, + file_stream: FileOutStream, + buffered_stream: BufferedOutStream(FileOutStream.Error), + + pub fn create(allocator: &mem.Allocator, dest_path: []const u8) !&BufferedAtomicFile { + // TODO with well defined copy elision we don't need this allocation + var self = try allocator.create(BufferedAtomicFile); + errdefer allocator.destroy(self); + + *self = BufferedAtomicFile { + .atomic_file = undefined, + .file_stream = undefined, + .buffered_stream = undefined, + }; + + self.atomic_file = try os.AtomicFile.init(allocator, dest_path, os.default_file_mode); + errdefer self.atomic_file.deinit(); + + self.file_stream = FileOutStream.init(&self.atomic_file.file); + self.buffered_stream = BufferedOutStream(FileOutStream.Error).init(&self.file_stream.stream); + return self; + } + + /// always call destroy, even after successful finish() + pub fn destroy(self: &BufferedAtomicFile) void { + const allocator = self.atomic_file.allocator; + self.atomic_file.deinit(); + allocator.destroy(self); + } + + pub fn finish(self: &BufferedAtomicFile) !void { + try self.buffered_stream.flush(); + try self.atomic_file.finish(); + } + + pub fn stream(self: &BufferedAtomicFile) &OutStream(FileOutStream.Error) { + return &self.buffered_stream.stream; + } +}; + +test "import io tests" { + comptime { + _ = @import("io_test.zig"); + } +} + diff --git a/std/io_test.zig b/std/io_test.zig index 1767a546ea..993ec84d20 100644 --- a/std/io_test.zig +++ b/std/io_test.zig @@ -13,11 +13,11 @@ test "write a file, read it, then delete it" { rng.fillBytes(data[0..]); const tmp_file_name = "temp_test_file.txt"; { - var file = try io.File.openWrite(tmp_file_name, allocator); + var file = try os.File.openWrite(allocator, tmp_file_name); defer file.close(); var file_out_stream = io.FileOutStream.init(&file); - var buf_stream = io.BufferedOutStream.init(&file_out_stream.stream); + var buf_stream = io.BufferedOutStream(io.FileOutStream.Error).init(&file_out_stream.stream); const st = &buf_stream.stream; try st.print("begin"); try st.write(data[0..]); @@ -25,7 +25,7 @@ test "write a file, read it, then delete it" { try buf_stream.flush(); } { - var file = try io.File.openRead(tmp_file_name, allocator); + var file = try os.File.openRead(allocator, tmp_file_name); defer file.close(); const file_size = try file.getEndPos(); @@ -33,7 +33,7 @@ test "write a file, read it, then delete it" { assert(file_size == expected_file_size); var file_in_stream = io.FileInStream.init(&file); - var buf_stream = io.BufferedInStream.init(&file_in_stream.stream); + var buf_stream = io.BufferedInStream(io.FileInStream.Error).init(&file_in_stream.stream); const st = &buf_stream.stream; const contents = try st.readAllAlloc(allocator, 2 * 1024); defer allocator.free(contents); diff --git a/std/linked_list.zig b/std/linked_list.zig index ffd68d5147..a6ab093341 100644 --- a/std/linked_list.zig +++ b/std/linked_list.zig @@ -190,7 +190,7 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na /// /// Returns: /// A pointer to the new node. - pub fn allocateNode(list: &Self, allocator: &Allocator) %&Node { + pub fn allocateNode(list: &Self, allocator: &Allocator) !&Node { comptime assert(!isIntrusive()); return allocator.create(Node); } @@ -213,7 +213,7 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na /// /// Returns: /// A pointer to the new node. - pub fn createNode(list: &Self, data: &const T, allocator: &Allocator) %&Node { + pub fn createNode(list: &Self, data: &const T, allocator: &Allocator) !&Node { comptime assert(!isIntrusive()); var node = try list.allocateNode(allocator); *node = Node.init(data); diff --git a/std/math/index.zig b/std/math/index.zig index 64d24a4dfd..f8668cc00d 100644 --- a/std/math/index.zig +++ b/std/math/index.zig @@ -191,30 +191,26 @@ test "math.max" { assert(max(i32(-1), i32(2)) == 2); } -error Overflow; -pub fn mul(comptime T: type, a: T, b: T) %T { +pub fn mul(comptime T: type, a: T, b: T) (error{Overflow}!T) { var answer: T = undefined; return if (@mulWithOverflow(T, a, b, &answer)) error.Overflow else answer; } -error Overflow; -pub fn add(comptime T: type, a: T, b: T) %T { +pub fn add(comptime T: type, a: T, b: T) (error{Overflow}!T) { var answer: T = undefined; return if (@addWithOverflow(T, a, b, &answer)) error.Overflow else answer; } -error Overflow; -pub fn sub(comptime T: type, a: T, b: T) %T { +pub fn sub(comptime T: type, a: T, b: T) (error{Overflow}!T) { var answer: T = undefined; return if (@subWithOverflow(T, a, b, &answer)) error.Overflow else answer; } -pub fn negate(x: var) %@typeOf(x) { +pub fn negate(x: var) !@typeOf(x) { return sub(@typeOf(x), 0, x); } -error Overflow; -pub fn shlExact(comptime T: type, a: T, shift_amt: Log2Int(T)) %T { +pub fn shlExact(comptime T: type, a: T, shift_amt: Log2Int(T)) !T { var answer: T = undefined; return if (@shlWithOverflow(T, a, shift_amt, &answer)) error.Overflow else answer; } @@ -323,8 +319,7 @@ fn testOverflow() void { } -error Overflow; -pub fn absInt(x: var) %@typeOf(x) { +pub fn absInt(x: var) !@typeOf(x) { const T = @typeOf(x); comptime assert(@typeId(T) == builtin.TypeId.Int); // must pass an integer to absInt comptime assert(T.is_signed); // must pass a signed integer to absInt @@ -347,9 +342,7 @@ fn testAbsInt() void { pub const absFloat = @import("fabs.zig").fabs; -error DivisionByZero; -error Overflow; -pub fn divTrunc(comptime T: type, numerator: T, denominator: T) %T { +pub fn divTrunc(comptime T: type, numerator: T, denominator: T) !T { @setRuntimeSafety(false); if (denominator == 0) return error.DivisionByZero; @@ -372,9 +365,7 @@ fn testDivTrunc() void { assert((divTrunc(f32, -5.0, 3.0) catch unreachable) == -1.0); } -error DivisionByZero; -error Overflow; -pub fn divFloor(comptime T: type, numerator: T, denominator: T) %T { +pub fn divFloor(comptime T: type, numerator: T, denominator: T) !T { @setRuntimeSafety(false); if (denominator == 0) return error.DivisionByZero; @@ -397,10 +388,7 @@ fn testDivFloor() void { assert((divFloor(f32, -5.0, 3.0) catch unreachable) == -2.0); } -error DivisionByZero; -error Overflow; -error UnexpectedRemainder; -pub fn divExact(comptime T: type, numerator: T, denominator: T) %T { +pub fn divExact(comptime T: type, numerator: T, denominator: T) !T { @setRuntimeSafety(false); if (denominator == 0) return error.DivisionByZero; @@ -428,9 +416,7 @@ fn testDivExact() void { if (divExact(f32, 5.0, 2.0)) |_| unreachable else |err| assert(err == error.UnexpectedRemainder); } -error DivisionByZero; -error NegativeDenominator; -pub fn mod(comptime T: type, numerator: T, denominator: T) %T { +pub fn mod(comptime T: type, numerator: T, denominator: T) !T { @setRuntimeSafety(false); if (denominator == 0) return error.DivisionByZero; @@ -455,9 +441,7 @@ fn testMod() void { if (mod(f32, 10, 0)) |_| unreachable else |err| assert(err == error.DivisionByZero); } -error DivisionByZero; -error NegativeDenominator; -pub fn rem(comptime T: type, numerator: T, denominator: T) %T { +pub fn rem(comptime T: type, numerator: T, denominator: T) !T { @setRuntimeSafety(false); if (denominator == 0) return error.DivisionByZero; @@ -505,8 +489,7 @@ test "math.absCast" { /// Returns the negation of the integer parameter. /// Result is a signed integer. -error Overflow; -pub fn negateCast(x: var) %@IntType(true, @typeOf(x).bit_count) { +pub fn negateCast(x: var) !@IntType(true, @typeOf(x).bit_count) { if (@typeOf(x).is_signed) return negate(x); @@ -532,8 +515,7 @@ test "math.negateCast" { /// Cast an integer to a different integer type. If the value doesn't fit, /// return an error. -error Overflow; -pub fn cast(comptime T: type, x: var) %T { +pub fn cast(comptime T: type, x: var) !T { comptime assert(@typeId(T) == builtin.TypeId.Int); // must pass an integer if (x > @maxValue(T)) { return error.Overflow; diff --git a/std/mem.zig b/std/mem.zig index 86bf5e2f3d..25c0648888 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -4,13 +4,13 @@ const assert = debug.assert; const math = std.math; const builtin = @import("builtin"); -error OutOfMemory; - pub const Allocator = struct { + const Error = error {OutOfMemory}; + /// Allocate byte_count bytes and return them in a slice, with the /// slice's pointer aligned at least to alignment bytes. /// The returned newly allocated memory is undefined. - allocFn: fn (self: &Allocator, byte_count: usize, alignment: u29) %[]u8, + allocFn: fn (self: &Allocator, byte_count: usize, alignment: u29) Error![]u8, /// If `new_byte_count > old_mem.len`: /// * `old_mem.len` is the same as what was returned from allocFn or reallocFn. @@ -21,12 +21,12 @@ pub const Allocator = struct { /// * alignment <= alignment of old_mem.ptr /// /// The returned newly allocated memory is undefined. - reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: u29) %[]u8, + reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: u29) Error![]u8, /// Guaranteed: `old_mem.len` is the same as what was returned from `allocFn` or `reallocFn` freeFn: fn (self: &Allocator, old_mem: []u8) void, - fn create(self: &Allocator, comptime T: type) %&T { + fn create(self: &Allocator, comptime T: type) !&T { const slice = try self.alloc(T, 1); return &slice[0]; } @@ -35,14 +35,14 @@ pub const Allocator = struct { self.free(ptr[0..1]); } - fn alloc(self: &Allocator, comptime T: type, n: usize) %[]T { + fn alloc(self: &Allocator, comptime T: type, n: usize) ![]T { return self.alignedAlloc(T, @alignOf(T), n); } fn alignedAlloc(self: &Allocator, comptime T: type, comptime alignment: u29, - n: usize) %[]align(alignment) T + n: usize) ![]align(alignment) T { - const byte_count = try math.mul(usize, @sizeOf(T), n); + const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory; const byte_slice = try self.allocFn(self, byte_count, alignment); // This loop should get optimized out in ReleaseFast mode for (byte_slice) |*byte| { @@ -51,19 +51,19 @@ pub const Allocator = struct { return ([]align(alignment) T)(@alignCast(alignment, byte_slice)); } - fn realloc(self: &Allocator, comptime T: type, old_mem: []T, n: usize) %[]T { + fn realloc(self: &Allocator, comptime T: type, old_mem: []T, n: usize) ![]T { return self.alignedRealloc(T, @alignOf(T), @alignCast(@alignOf(T), old_mem), n); } fn alignedRealloc(self: &Allocator, comptime T: type, comptime alignment: u29, - old_mem: []align(alignment) T, n: usize) %[]align(alignment) T + old_mem: []align(alignment) T, n: usize) ![]align(alignment) T { if (old_mem.len == 0) { return self.alloc(T, n); } const old_byte_slice = ([]u8)(old_mem); - const byte_count = try math.mul(usize, @sizeOf(T), n); + const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory; const byte_slice = try self.reallocFn(self, old_byte_slice, byte_count, alignment); // This loop should get optimized out in ReleaseFast mode for (byte_slice[old_byte_slice.len..]) |*byte| { @@ -123,7 +123,7 @@ pub const FixedBufferAllocator = struct { }; } - fn alloc(allocator: &Allocator, n: usize, alignment: u29) %[]u8 { + fn alloc(allocator: &Allocator, n: usize, alignment: u29) ![]u8 { const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator); const addr = @ptrToInt(&self.buffer[self.end_index]); const rem = @rem(addr, alignment); @@ -138,7 +138,7 @@ pub const FixedBufferAllocator = struct { return result; } - fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) %[]u8 { + fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 { if (new_size <= old_mem.len) { return old_mem[0..new_size]; } else { @@ -197,7 +197,7 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) bool { } /// Copies ::m to newly allocated memory. Caller is responsible to free it. -pub fn dupe(allocator: &Allocator, comptime T: type, m: []const T) %[]T { +pub fn dupe(allocator: &Allocator, comptime T: type, m: []const T) ![]T { const new_buf = try allocator.alloc(T, m.len); copy(T, new_buf, m); return new_buf; @@ -428,7 +428,7 @@ const SplitIterator = struct { /// Naively combines a series of strings with a separator. /// Allocates memory for the result, which must be freed by the caller. -pub fn join(allocator: &Allocator, sep: u8, strings: ...) %[]u8 { +pub fn join(allocator: &Allocator, sep: u8, strings: ...) ![]u8 { comptime assert(strings.len >= 1); var total_strings_len: usize = strings.len; // 1 sep per string { diff --git a/std/net.zig b/std/net.zig index 4fbfd9b9aa..1140b6449b 100644 --- a/std/net.zig +++ b/std/net.zig @@ -5,19 +5,10 @@ const endian = std.endian; // TODO don't trust this file, it bit rotted. start over -error SigInterrupt; -error Io; -error TimedOut; -error ConnectionReset; -error ConnectionRefused; -error OutOfMemory; -error NotSocket; -error BadFd; - const Connection = struct { socket_fd: i32, - pub fn send(c: Connection, buf: []const u8) %usize { + pub fn send(c: Connection, buf: []const u8) !usize { const send_ret = linux.sendto(c.socket_fd, buf.ptr, buf.len, 0, null, 0); const send_err = linux.getErrno(send_ret); switch (send_err) { @@ -31,7 +22,7 @@ const Connection = struct { } } - pub fn recv(c: Connection, buf: []u8) %[]u8 { + pub fn recv(c: Connection, buf: []u8) ![]u8 { const recv_ret = linux.recvfrom(c.socket_fd, buf.ptr, buf.len, 0, null, null); const recv_err = linux.getErrno(recv_ret); switch (recv_err) { @@ -48,7 +39,7 @@ const Connection = struct { } } - pub fn close(c: Connection) %void { + pub fn close(c: Connection) !void { switch (linux.getErrno(linux.close(c.socket_fd))) { 0 => return, linux.EBADF => unreachable, @@ -66,7 +57,7 @@ const Address = struct { sort_key: i32, }; -pub fn lookup(hostname: []const u8, out_addrs: []Address) %[]Address { +pub fn lookup(hostname: []const u8, out_addrs: []Address) ![]Address { if (hostname.len == 0) { unreachable; // TODO @@ -75,7 +66,7 @@ pub fn lookup(hostname: []const u8, out_addrs: []Address) %[]Address { unreachable; // TODO } -pub fn connectAddr(addr: &Address, port: u16) %Connection { +pub fn connectAddr(addr: &Address, port: u16) !Connection { const socket_ret = linux.socket(addr.family, linux.SOCK_STREAM, linux.PROTO_tcp); const socket_err = linux.getErrno(socket_ret); if (socket_err > 0) { @@ -118,7 +109,7 @@ pub fn connectAddr(addr: &Address, port: u16) %Connection { }; } -pub fn connect(hostname: []const u8, port: u16) %Connection { +pub fn connect(hostname: []const u8, port: u16) !Connection { var addrs_buf: [1]Address = undefined; const addrs_slice = try lookup(hostname, addrs_buf[0..]); const main_addr = &addrs_slice[0]; @@ -126,9 +117,7 @@ pub fn connect(hostname: []const u8, port: u16) %Connection { return connectAddr(main_addr, port); } -error InvalidIpLiteral; - -pub fn parseIpLiteral(buf: []const u8) %Address { +pub fn parseIpLiteral(buf: []const u8) !Address { return error.InvalidIpLiteral; } @@ -146,12 +135,7 @@ fn hexDigit(c: u8) u8 { } } -error InvalidChar; -error Overflow; -error JunkAtEnd; -error Incomplete; - -fn parseIp6(buf: []const u8) %Address { +fn parseIp6(buf: []const u8) !Address { var result: Address = undefined; result.family = linux.AF_INET6; result.scope_id = 0; @@ -232,7 +216,7 @@ fn parseIp6(buf: []const u8) %Address { return error.Incomplete; } -fn parseIp4(buf: []const u8) %u32 { +fn parseIp4(buf: []const u8) !u32 { var result: u32 = undefined; const out_ptr = ([]u8)((&result)[0..1]); diff --git a/std/os/child_process.zig b/std/os/child_process.zig index f4709ce75a..c85202c9ed 100644 --- a/std/os/child_process.zig +++ b/std/os/child_process.zig @@ -13,10 +13,6 @@ const builtin = @import("builtin"); const Os = builtin.Os; const LinkedList = std.LinkedList; -error PermissionDenied; -error ProcessNotFound; -error InvalidName; - var children_nodes = LinkedList(&ChildProcess).init(); const is_windows = builtin.os == Os.windows; @@ -28,11 +24,11 @@ pub const ChildProcess = struct { pub allocator: &mem.Allocator, - pub stdin: ?io.File, - pub stdout: ?io.File, - pub stderr: ?io.File, + pub stdin: ?os.File, + pub stdout: ?os.File, + pub stderr: ?os.File, - pub term: ?%Term, + pub term: ?(SpawnError!Term), pub argv: []const []const u8, @@ -58,6 +54,25 @@ pub const ChildProcess = struct { err_pipe: if (is_windows) void else [2]i32, llnode: if (is_windows) void else LinkedList(&ChildProcess).Node, + pub const SpawnError = error { + ProcessFdQuotaExceeded, + Unexpected, + NotDir, + SystemResources, + FileNotFound, + NameTooLong, + SymLinkLoop, + FileSystem, + OutOfMemory, + AccessDenied, + PermissionDenied, + InvalidUserId, + ResourceLimitReached, + InvalidExe, + IsDir, + FileBusy, + }; + pub const Term = union(enum) { Exited: i32, Signal: i32, @@ -74,7 +89,7 @@ pub const ChildProcess = struct { /// First argument in argv is the executable. /// On success must call deinit. - pub fn init(argv: []const []const u8, allocator: &mem.Allocator) %&ChildProcess { + pub fn init(argv: []const []const u8, allocator: &mem.Allocator) !&ChildProcess { const child = try allocator.create(ChildProcess); errdefer allocator.destroy(child); @@ -103,7 +118,7 @@ pub const ChildProcess = struct { return child; } - pub fn setUserName(self: &ChildProcess, name: []const u8) %void { + pub fn setUserName(self: &ChildProcess, name: []const u8) !void { const user_info = try os.getUserInfo(name); self.uid = user_info.uid; self.gid = user_info.gid; @@ -111,7 +126,7 @@ pub const ChildProcess = struct { /// onTerm can be called before `spawn` returns. /// On success must call `kill` or `wait`. - pub fn spawn(self: &ChildProcess) %void { + pub fn spawn(self: &ChildProcess) !void { if (is_windows) { return self.spawnWindows(); } else { @@ -119,13 +134,13 @@ pub const ChildProcess = struct { } } - pub fn spawnAndWait(self: &ChildProcess) %Term { + pub fn spawnAndWait(self: &ChildProcess) !Term { try self.spawn(); return self.wait(); } /// Forcibly terminates child process and then cleans up all resources. - pub fn kill(self: &ChildProcess) %Term { + pub fn kill(self: &ChildProcess) !Term { if (is_windows) { return self.killWindows(1); } else { @@ -133,7 +148,7 @@ pub const ChildProcess = struct { } } - pub fn killWindows(self: &ChildProcess, exit_code: windows.UINT) %Term { + pub fn killWindows(self: &ChildProcess, exit_code: windows.UINT) !Term { if (self.term) |term| { self.cleanupStreams(); return term; @@ -145,11 +160,11 @@ pub const ChildProcess = struct { else => os.unexpectedErrorWindows(err), }; } - self.waitUnwrappedWindows(); + try self.waitUnwrappedWindows(); return ??self.term; } - pub fn killPosix(self: &ChildProcess) %Term { + pub fn killPosix(self: &ChildProcess) !Term { block_SIGCHLD(); defer restore_SIGCHLD(); @@ -172,7 +187,7 @@ pub const ChildProcess = struct { } /// Blocks until child process terminates and then cleans up all resources. - pub fn wait(self: &ChildProcess) %Term { + pub fn wait(self: &ChildProcess) !Term { if (is_windows) { return self.waitWindows(); } else { @@ -189,7 +204,7 @@ pub const ChildProcess = struct { /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. /// If it succeeds, the caller owns result.stdout and result.stderr memory. pub fn exec(allocator: &mem.Allocator, argv: []const []const u8, cwd: ?[]const u8, - env_map: ?&const BufMap, max_output_size: usize) %ExecResult + env_map: ?&const BufMap, max_output_size: usize) !ExecResult { const child = try ChildProcess.init(argv, allocator); defer child.deinit(); @@ -220,7 +235,7 @@ pub const ChildProcess = struct { }; } - fn waitWindows(self: &ChildProcess) %Term { + fn waitWindows(self: &ChildProcess) !Term { if (self.term) |term| { self.cleanupStreams(); return term; @@ -230,7 +245,7 @@ pub const ChildProcess = struct { return ??self.term; } - fn waitPosix(self: &ChildProcess) %Term { + fn waitPosix(self: &ChildProcess) !Term { block_SIGCHLD(); defer restore_SIGCHLD(); @@ -247,10 +262,10 @@ pub const ChildProcess = struct { self.allocator.destroy(self); } - fn waitUnwrappedWindows(self: &ChildProcess) %void { + fn waitUnwrappedWindows(self: &ChildProcess) !void { const result = os.windowsWaitSingle(self.handle, windows.INFINITE); - self.term = (%Term)(x: { + self.term = (SpawnError!Term)(x: { var exit_code: windows.DWORD = undefined; if (windows.GetExitCodeProcess(self.handle, &exit_code) == 0) { break :x Term { .Unknown = 0 }; @@ -295,7 +310,7 @@ pub const ChildProcess = struct { if (self.stderr) |*stderr| { stderr.close(); self.stderr = null; } } - fn cleanupAfterWait(self: &ChildProcess, status: i32) %Term { + fn cleanupAfterWait(self: &ChildProcess, status: i32) !Term { children_nodes.remove(&self.llnode); defer { @@ -313,7 +328,7 @@ pub const ChildProcess = struct { // Here we potentially return the fork child's error // from the parent pid. if (err_int != @maxValue(ErrInt)) { - return error(err_int); + return SpawnError(err_int); } return statusToTerm(status); @@ -331,7 +346,7 @@ pub const ChildProcess = struct { ; } - fn spawnPosix(self: &ChildProcess) %void { + fn spawnPosix(self: &ChildProcess) !void { // TODO atomically set a flag saying that we already did this install_SIGCHLD_handler(); @@ -345,11 +360,14 @@ pub const ChildProcess = struct { errdefer if (self.stderr_behavior == StdIo.Pipe) { destroyPipe(stderr_pipe); }; const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); - const dev_null_fd = if (any_ignore) - try os.posixOpen("/dev/null", posix.O_RDWR, 0, null) - else - undefined - ; + const dev_null_fd = if (any_ignore) blk: { + const dev_null_path = "/dev/null"; + var fixed_buffer_mem: [dev_null_path.len + 1]u8 = undefined; + var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + break :blk try os.posixOpen(&fixed_allocator.allocator, "/dev/null", posix.O_RDWR, 0); + } else blk: { + break :blk undefined; + }; defer { if (any_ignore) os.close(dev_null_fd); } var env_map_owned: BufMap = undefined; @@ -410,17 +428,17 @@ pub const ChildProcess = struct { // we are the parent const pid = i32(pid_result); if (self.stdin_behavior == StdIo.Pipe) { - self.stdin = io.File.openHandle(stdin_pipe[1]); + self.stdin = os.File.openHandle(stdin_pipe[1]); } else { self.stdin = null; } if (self.stdout_behavior == StdIo.Pipe) { - self.stdout = io.File.openHandle(stdout_pipe[0]); + self.stdout = os.File.openHandle(stdout_pipe[0]); } else { self.stdout = null; } if (self.stderr_behavior == StdIo.Pipe) { - self.stderr = io.File.openHandle(stderr_pipe[0]); + self.stderr = os.File.openHandle(stderr_pipe[0]); } else { self.stderr = null; } @@ -440,7 +458,7 @@ pub const ChildProcess = struct { if (self.stderr_behavior == StdIo.Pipe) { os.close(stderr_pipe[1]); } } - fn spawnWindows(self: &ChildProcess) %void { + fn spawnWindows(self: &ChildProcess) !void { const saAttr = windows.SECURITY_ATTRIBUTES { .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), .bInheritHandle = windows.TRUE, @@ -451,12 +469,15 @@ pub const ChildProcess = struct { self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); - const nul_handle = if (any_ignore) - try os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, - windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null) - else - undefined - ; + const nul_handle = if (any_ignore) blk: { + const nul_file_path = "NUL"; + var fixed_buffer_mem: [nul_file_path.len + 1]u8 = undefined; + var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + break :blk try os.windowsOpen(&fixed_allocator.allocator, "NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, + windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL); + } else blk: { + break :blk undefined; + }; defer { if (any_ignore) os.close(nul_handle); } if (any_ignore) { try windowsSetHandleInfo(nul_handle, windows.HANDLE_FLAG_INHERIT, 0); @@ -599,17 +620,17 @@ pub const ChildProcess = struct { }; if (g_hChildStd_IN_Wr) |h| { - self.stdin = io.File.openHandle(h); + self.stdin = os.File.openHandle(h); } else { self.stdin = null; } if (g_hChildStd_OUT_Rd) |h| { - self.stdout = io.File.openHandle(h); + self.stdout = os.File.openHandle(h); } else { self.stdout = null; } if (g_hChildStd_ERR_Rd) |h| { - self.stderr = io.File.openHandle(h); + self.stderr = os.File.openHandle(h); } else { self.stderr = null; } @@ -623,7 +644,7 @@ pub const ChildProcess = struct { if (self.stdout_behavior == StdIo.Pipe) { os.close(??g_hChildStd_OUT_Wr); } } - fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) %void { + fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { switch (stdio) { StdIo.Pipe => try os.posixDup2(pipe_fd, std_fileno), StdIo.Close => os.close(std_fileno), @@ -635,7 +656,7 @@ pub const ChildProcess = struct { }; fn windowsCreateProcess(app_name: &u8, cmd_line: &u8, envp_ptr: ?&u8, cwd_ptr: ?&u8, - lpStartupInfo: &windows.STARTUPINFOA, lpProcessInformation: &windows.PROCESS_INFORMATION) %void + lpStartupInfo: &windows.STARTUPINFOA, lpProcessInformation: &windows.PROCESS_INFORMATION) !void { if (windows.CreateProcessA(app_name, cmd_line, null, null, windows.TRUE, 0, @ptrCast(?&c_void, envp_ptr), cwd_ptr, lpStartupInfo, lpProcessInformation) == 0) @@ -655,7 +676,7 @@ fn windowsCreateProcess(app_name: &u8, cmd_line: &u8, envp_ptr: ?&u8, cwd_ptr: ? /// Caller must dealloc. /// Guarantees a null byte at result[result.len]. -fn windowsCreateCommandLine(allocator: &mem.Allocator, argv: []const []const u8) %[]u8 { +fn windowsCreateCommandLine(allocator: &mem.Allocator, argv: []const []const u8) ![]u8 { var buf = try Buffer.initSize(allocator, 0); defer buf.deinit(); @@ -700,7 +721,7 @@ fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { // a namespace field lookup const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES; -fn windowsMakePipe(rd: &windows.HANDLE, wr: &windows.HANDLE, sattr: &const SECURITY_ATTRIBUTES) %void { +fn windowsMakePipe(rd: &windows.HANDLE, wr: &windows.HANDLE, sattr: &const SECURITY_ATTRIBUTES) !void { if (windows.CreatePipe(rd, wr, sattr, 0) == 0) { const err = windows.GetLastError(); return switch (err) { @@ -709,7 +730,7 @@ fn windowsMakePipe(rd: &windows.HANDLE, wr: &windows.HANDLE, sattr: &const SECUR } } -fn windowsSetHandleInfo(h: windows.HANDLE, mask: windows.DWORD, flags: windows.DWORD) %void { +fn windowsSetHandleInfo(h: windows.HANDLE, mask: windows.DWORD, flags: windows.DWORD) !void { if (windows.SetHandleInformation(h, mask, flags) == 0) { const err = windows.GetLastError(); return switch (err) { @@ -718,7 +739,7 @@ fn windowsSetHandleInfo(h: windows.HANDLE, mask: windows.DWORD, flags: windows.D } } -fn windowsMakePipeIn(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &const SECURITY_ATTRIBUTES) %void { +fn windowsMakePipeIn(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &const SECURITY_ATTRIBUTES) !void { var rd_h: windows.HANDLE = undefined; var wr_h: windows.HANDLE = undefined; try windowsMakePipe(&rd_h, &wr_h, sattr); @@ -728,7 +749,7 @@ fn windowsMakePipeIn(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &const S *wr = wr_h; } -fn windowsMakePipeOut(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &const SECURITY_ATTRIBUTES) %void { +fn windowsMakePipeOut(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &const SECURITY_ATTRIBUTES) !void { var rd_h: windows.HANDLE = undefined; var wr_h: windows.HANDLE = undefined; try windowsMakePipe(&rd_h, &wr_h, sattr); @@ -738,7 +759,7 @@ fn windowsMakePipeOut(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &const *wr = wr_h; } -fn makePipe() %[2]i32 { +fn makePipe() ![2]i32 { var fds: [2]i32 = undefined; const err = posix.getErrno(posix.pipe(&fds)); if (err > 0) { @@ -757,20 +778,20 @@ fn destroyPipe(pipe: &const [2]i32) void { // Child of fork calls this to report an error to the fork parent. // Then the child exits. -fn forkChildErrReport(fd: i32, err: error) noreturn { +fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { _ = writeIntFd(fd, ErrInt(err)); posix.exit(1); } const ErrInt = @IntType(false, @sizeOf(error) * 8); -fn writeIntFd(fd: i32, value: ErrInt) %void { +fn writeIntFd(fd: i32, value: ErrInt) !void { var bytes: [@sizeOf(ErrInt)]u8 = undefined; mem.writeInt(bytes[0..], value, builtin.endian); os.posixWrite(fd, bytes[0..]) catch return error.SystemResources; } -fn readIntFd(fd: i32) %ErrInt { +fn readIntFd(fd: i32) !ErrInt { var bytes: [@sizeOf(ErrInt)]u8 = undefined; os.posixRead(fd, bytes[0..]) catch return error.SystemResources; return mem.readInt(bytes[0..], ErrInt, builtin.endian); diff --git a/std/os/file.zig b/std/os/file.zig new file mode 100644 index 0000000000..772fbf7c73 --- /dev/null +++ b/std/os/file.zig @@ -0,0 +1,311 @@ +const std = @import("../index.zig"); +const builtin = @import("builtin"); +const os = std.os; +const mem = std.mem; +const math = std.math; +const assert = std.debug.assert; +const posix = os.posix; +const windows = os.windows; +const Os = builtin.Os; + +const is_posix = builtin.os != builtin.Os.windows; +const is_windows = builtin.os == builtin.Os.windows; + +pub const File = struct { + /// The OS-specific file descriptor or file handle. + handle: os.FileHandle, + + const OpenError = os.WindowsOpenError || os.PosixOpenError; + + /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. + /// Call close to clean up. + pub fn openRead(allocator: &mem.Allocator, path: []const u8) OpenError!File { + if (is_posix) { + const flags = posix.O_LARGEFILE|posix.O_RDONLY; + const fd = try os.posixOpen(allocator, path, flags, 0); + return openHandle(fd); + } else if (is_windows) { + const handle = try os.windowsOpen(allocator, path, windows.GENERIC_READ, windows.FILE_SHARE_READ, + windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL); + return openHandle(handle); + } else { + @compileError("TODO implement openRead for this OS"); + } + } + + /// Calls `openWriteMode` with os.default_file_mode for the mode. + pub fn openWrite(allocator: &mem.Allocator, path: []const u8) OpenError!File { + return openWriteMode(allocator, path, os.default_file_mode); + + } + + /// If the path does not exist it will be created. + /// If a file already exists in the destination it will be truncated. + /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. + /// Call close to clean up. + pub fn openWriteMode(allocator: &mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File { + if (is_posix) { + const flags = posix.O_LARGEFILE|posix.O_WRONLY|posix.O_CREAT|posix.O_CLOEXEC|posix.O_TRUNC; + const fd = try os.posixOpen(allocator, path, flags, file_mode); + return openHandle(fd); + } else if (is_windows) { + const handle = try os.windowsOpen(allocator, path, windows.GENERIC_WRITE, + windows.FILE_SHARE_WRITE|windows.FILE_SHARE_READ|windows.FILE_SHARE_DELETE, + windows.CREATE_ALWAYS, windows.FILE_ATTRIBUTE_NORMAL); + return openHandle(handle); + } else { + @compileError("TODO implement openWriteMode for this OS"); + } + + } + + /// If the path does not exist it will be created. + /// If a file already exists in the destination this returns OpenError.PathAlreadyExists + /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. + /// Call close to clean up. + pub fn openWriteNoClobber(allocator: &mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File { + if (is_posix) { + const flags = posix.O_LARGEFILE|posix.O_WRONLY|posix.O_CREAT|posix.O_CLOEXEC|posix.O_EXCL; + const fd = try os.posixOpen(allocator, path, flags, file_mode); + return openHandle(fd); + } else if (is_windows) { + const handle = try os.windowsOpen(allocator, path, windows.GENERIC_WRITE, + windows.FILE_SHARE_WRITE|windows.FILE_SHARE_READ|windows.FILE_SHARE_DELETE, + windows.CREATE_NEW, windows.FILE_ATTRIBUTE_NORMAL); + return openHandle(handle); + } else { + @compileError("TODO implement openWriteMode for this OS"); + } + + } + + pub fn openHandle(handle: os.FileHandle) File { + return File { + .handle = handle, + }; + } + + + /// Upon success, the stream is in an uninitialized state. To continue using it, + /// you must use the open() function. + pub fn close(self: &File) void { + os.close(self.handle); + self.handle = undefined; + } + + /// Calls `os.isTty` on `self.handle`. + pub fn isTty(self: &File) bool { + return os.isTty(self.handle); + } + + pub fn seekForward(self: &File, amount: isize) !void { + switch (builtin.os) { + Os.linux, Os.macosx, Os.ios => { + const result = posix.lseek(self.handle, amount, posix.SEEK_CUR); + const err = posix.getErrno(result); + if (err > 0) { + return switch (err) { + posix.EBADF => error.BadFd, + posix.EINVAL => error.Unseekable, + posix.EOVERFLOW => error.Unseekable, + posix.ESPIPE => error.Unseekable, + posix.ENXIO => error.Unseekable, + else => os.unexpectedErrorPosix(err), + }; + } + }, + Os.windows => { + if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) { + const err = windows.GetLastError(); + return switch (err) { + windows.ERROR.INVALID_PARAMETER => error.BadFd, + else => os.unexpectedErrorWindows(err), + }; + } + }, + else => @compileError("unsupported OS"), + } + } + + pub fn seekTo(self: &File, pos: usize) !void { + switch (builtin.os) { + Os.linux, Os.macosx, Os.ios => { + const ipos = try math.cast(isize, pos); + const result = posix.lseek(self.handle, ipos, posix.SEEK_SET); + const err = posix.getErrno(result); + if (err > 0) { + return switch (err) { + posix.EBADF => error.BadFd, + posix.EINVAL => error.Unseekable, + posix.EOVERFLOW => error.Unseekable, + posix.ESPIPE => error.Unseekable, + posix.ENXIO => error.Unseekable, + else => os.unexpectedErrorPosix(err), + }; + } + }, + Os.windows => { + const ipos = try math.cast(isize, pos); + if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) { + const err = windows.GetLastError(); + return switch (err) { + windows.ERROR.INVALID_PARAMETER => error.BadFd, + else => os.unexpectedErrorWindows(err), + }; + } + }, + else => @compileError("unsupported OS: " ++ @tagName(builtin.os)), + } + } + + pub fn getPos(self: &File) !usize { + switch (builtin.os) { + Os.linux, Os.macosx, Os.ios => { + const result = posix.lseek(self.handle, 0, posix.SEEK_CUR); + const err = posix.getErrno(result); + if (err > 0) { + return switch (err) { + posix.EBADF => error.BadFd, + posix.EINVAL => error.Unseekable, + posix.EOVERFLOW => error.Unseekable, + posix.ESPIPE => error.Unseekable, + posix.ENXIO => error.Unseekable, + else => os.unexpectedErrorPosix(err), + }; + } + return result; + }, + Os.windows => { + var pos : windows.LARGE_INTEGER = undefined; + if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) { + const err = windows.GetLastError(); + return switch (err) { + windows.ERROR.INVALID_PARAMETER => error.BadFd, + else => os.unexpectedErrorWindows(err), + }; + } + + assert(pos >= 0); + if (@sizeOf(@typeOf(pos)) > @sizeOf(usize)) { + if (pos > @maxValue(usize)) { + return error.FilePosLargerThanPointerRange; + } + } + + return usize(pos); + }, + else => @compileError("unsupported OS"), + } + } + + pub fn getEndPos(self: &File) !usize { + if (is_posix) { + var stat: posix.Stat = undefined; + const err = posix.getErrno(posix.fstat(self.handle, &stat)); + if (err > 0) { + return switch (err) { + posix.EBADF => error.BadFd, + posix.ENOMEM => error.SystemResources, + else => os.unexpectedErrorPosix(err), + }; + } + + return usize(stat.size); + } else if (is_windows) { + var file_size: windows.LARGE_INTEGER = undefined; + if (windows.GetFileSizeEx(self.handle, &file_size) == 0) { + const err = windows.GetLastError(); + return switch (err) { + else => os.unexpectedErrorWindows(err), + }; + } + if (file_size < 0) + return error.Overflow; + return math.cast(usize, u64(file_size)); + } else { + @compileError("TODO support getEndPos on this OS"); + } + } + + pub const ModeError = error { + BadFd, + SystemResources, + Unexpected, + }; + + fn mode(self: &File) ModeError!FileMode { + if (is_posix) { + var stat: posix.Stat = undefined; + const err = posix.getErrno(posix.fstat(self.handle, &stat)); + if (err > 0) { + return switch (err) { + posix.EBADF => error.BadFd, + posix.ENOMEM => error.SystemResources, + else => os.unexpectedErrorPosix(err), + }; + } + + return stat.mode; + } else if (is_windows) { + return {}; + } else { + @compileError("TODO support file mode on this OS"); + } + } + + pub const ReadError = error {}; + + pub fn read(self: &File, buffer: []u8) !usize { + if (is_posix) { + var index: usize = 0; + while (index < buffer.len) { + const amt_read = posix.read(self.handle, &buffer[index], buffer.len - index); + const read_err = posix.getErrno(amt_read); + if (read_err > 0) { + switch (read_err) { + posix.EINTR => continue, + posix.EINVAL => unreachable, + posix.EFAULT => unreachable, + posix.EBADF => return error.BadFd, + posix.EIO => return error.Io, + else => return os.unexpectedErrorPosix(read_err), + } + } + if (amt_read == 0) return index; + index += amt_read; + } + return index; + } else if (is_windows) { + var index: usize = 0; + while (index < buffer.len) { + const want_read_count = windows.DWORD(math.min(windows.DWORD(@maxValue(windows.DWORD)), buffer.len - index)); + var amt_read: windows.DWORD = undefined; + if (windows.ReadFile(self.handle, @ptrCast(&c_void, &buffer[index]), want_read_count, &amt_read, null) == 0) { + const err = windows.GetLastError(); + return switch (err) { + windows.ERROR.OPERATION_ABORTED => continue, + windows.ERROR.BROKEN_PIPE => return index, + else => os.unexpectedErrorWindows(err), + }; + } + if (amt_read == 0) return index; + index += amt_read; + } + return index; + } else { + unreachable; + } + } + + pub const WriteError = os.WindowsWriteError || os.PosixWriteError; + + fn write(self: &File, bytes: []const u8) WriteError!void { + if (is_posix) { + try os.posixWrite(self.handle, bytes); + } else if (is_windows) { + try os.windowsWrite(self.handle, bytes); + } else { + @compileError("Unsupported OS"); + } + } +}; diff --git a/std/os/get_user_id.zig b/std/os/get_user_id.zig index 68cb268169..11410ffa64 100644 --- a/std/os/get_user_id.zig +++ b/std/os/get_user_id.zig @@ -9,7 +9,7 @@ pub const UserInfo = struct { }; /// POSIX function which gets a uid from username. -pub fn getUserInfo(name: []const u8) %UserInfo { +pub fn getUserInfo(name: []const u8) !UserInfo { return switch (builtin.os) { Os.linux, Os.macosx, Os.ios => posixGetUserInfo(name), else => @compileError("Unsupported OS"), @@ -24,13 +24,10 @@ const State = enum { ReadGroupId, }; -error UserNotFound; -error CorruptPasswordFile; - // TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else // like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`. -pub fn posixGetUserInfo(name: []const u8) %UserInfo { +pub fn posixGetUserInfo(name: []const u8) !UserInfo { var in_stream = try io.InStream.open("/etc/passwd", null); defer in_stream.close(); diff --git a/std/os/index.zig b/std/os/index.zig index a543f27be4..2131e72760 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -15,13 +15,18 @@ pub const posix = switch(builtin.os) { else => @compileError("Unsupported OS"), }; -pub const max_noalloc_path_len = 1024; pub const ChildProcess = @import("child_process.zig").ChildProcess; pub const path = @import("path.zig"); +pub const File = @import("file.zig").File; -pub const line_sep = switch (builtin.os) { - Os.windows => "\r\n", - else => "\n", +pub const FileMode = switch (builtin.os) { + Os.windows => void, + else => u32, +}; + +pub const default_file_mode = switch (builtin.os) { + Os.windows => {}, + else => 0o666, }; pub const page_size = 4 * 1024; @@ -38,6 +43,10 @@ pub const windowsLoadDll = windows_util.windowsLoadDll; pub const windowsUnloadDll = windows_util.windowsUnloadDll; pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock; +pub const WindowsWaitError = windows_util.WaitError; +pub const WindowsOpenError = windows_util.OpenError; +pub const WindowsWriteError = windows_util.WriteError; + pub const FileHandle = if (is_windows) windows.HANDLE else i32; const debug = std.debug; @@ -57,25 +66,10 @@ const ArrayList = std.ArrayList; const Buffer = std.Buffer; const math = std.math; -error SystemResources; -error AccessDenied; -error InvalidExe; -error FileSystem; -error IsDir; -error FileNotFound; -error FileBusy; -error PathAlreadyExists; -error SymLinkLoop; -error ReadOnlyFileSystem; -error LinkQuotaExceeded; -error RenameAcrossMountPoints; -error DirNotEmpty; -error WouldBlock; - /// Fills `buf` with random bytes. If linking against libc, this calls the /// appropriate OS-specific library call. Otherwise it uses the zig standard /// library implementation. -pub fn getRandomBytes(buf: []u8) %void { +pub fn getRandomBytes(buf: []u8) !void { switch (builtin.os) { Os.linux => while (true) { // TODO check libc version and potentially call c.getrandom. @@ -188,7 +182,7 @@ pub fn close(handle: FileHandle) void { } /// Calls POSIX read, and keeps trying if it gets interrupted. -pub fn posixRead(fd: i32, buf: []u8) %void { +pub fn posixRead(fd: i32, buf: []u8) !void { // Linux can return EINVAL when read amount is > 0x7ffff000 // See https://github.com/zig-lang/zig/pull/743#issuecomment-363158274 const max_buf_len = 0x7ffff000; @@ -214,17 +208,21 @@ pub fn posixRead(fd: i32, buf: []u8) %void { } } -error WouldBlock; -error FileClosed; -error DestinationAddressRequired; -error DiskQuota; -error FileTooBig; -error InputOutput; -error NoSpaceLeft; -error BrokenPipe; +pub const PosixWriteError = error { + WouldBlock, + FileClosed, + DestinationAddressRequired, + DiskQuota, + FileTooBig, + InputOutput, + NoSpaceLeft, + AccessDenied, + BrokenPipe, + Unexpected, +}; /// Calls POSIX write, and keeps trying if it gets interrupted. -pub fn posixWrite(fd: i32, bytes: []const u8) %void { +pub fn posixWrite(fd: i32, bytes: []const u8) !void { // Linux can return EINVAL when write amount is > 0x7ffff000 // See https://github.com/zig-lang/zig/pull/743#issuecomment-363165856 const max_bytes_len = 0x7ffff000; @@ -238,15 +236,15 @@ pub fn posixWrite(fd: i32, bytes: []const u8) %void { return switch (write_err) { posix.EINTR => continue, posix.EINVAL, posix.EFAULT => unreachable, - posix.EAGAIN => error.WouldBlock, - posix.EBADF => error.FileClosed, - posix.EDESTADDRREQ => error.DestinationAddressRequired, - posix.EDQUOT => error.DiskQuota, - posix.EFBIG => error.FileTooBig, - posix.EIO => error.InputOutput, - posix.ENOSPC => error.NoSpaceLeft, - posix.EPERM => error.AccessDenied, - posix.EPIPE => error.BrokenPipe, + posix.EAGAIN => PosixWriteError.WouldBlock, + posix.EBADF => PosixWriteError.FileClosed, + posix.EDESTADDRREQ => PosixWriteError.DestinationAddressRequired, + posix.EDQUOT => PosixWriteError.DiskQuota, + posix.EFBIG => PosixWriteError.FileTooBig, + posix.EIO => PosixWriteError.InputOutput, + posix.ENOSPC => PosixWriteError.NoSpaceLeft, + posix.EPERM => PosixWriteError.AccessDenied, + posix.EPIPE => PosixWriteError.BrokenPipe, else => unexpectedErrorPosix(write_err), }; } @@ -254,66 +252,66 @@ pub fn posixWrite(fd: i32, bytes: []const u8) %void { } } -/// ::file_path may need to be copied in memory to add a null terminating byte. In this case -/// a fixed size buffer of size ::max_noalloc_path_len is an attempted solution. If the fixed -/// size buffer is too small, and the provided allocator is null, ::error.NameTooLong is returned. -/// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory. +pub const PosixOpenError = error { + OutOfMemory, + AccessDenied, + FileTooBig, + IsDir, + SymLinkLoop, + ProcessFdQuotaExceeded, + NameTooLong, + SystemFdQuotaExceeded, + NoDevice, + PathNotFound, + SystemResources, + NoSpaceLeft, + NotDir, + PathAlreadyExists, + Unexpected, +}; + +/// ::file_path needs to be copied in memory to add a null terminating byte. /// Calls POSIX open, keeps trying if it gets interrupted, and translates /// the return value into zig errors. -pub fn posixOpen(file_path: []const u8, flags: u32, perm: usize, allocator: ?&Allocator) %i32 { - var stack_buf: [max_noalloc_path_len]u8 = undefined; - var path0: []u8 = undefined; - var need_free = false; +pub fn posixOpen(allocator: &Allocator, file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 { + const path_with_null = try cstr.addNullByte(allocator, file_path); + defer allocator.free(path_with_null); - if (file_path.len < stack_buf.len) { - path0 = stack_buf[0..file_path.len + 1]; - } else if (allocator) |a| { - path0 = try a.alloc(u8, file_path.len + 1); - need_free = true; - } else { - return error.NameTooLong; - } - defer if (need_free) { - (??allocator).free(path0); - }; - mem.copy(u8, path0, file_path); - path0[file_path.len] = 0; - - return posixOpenC(path0.ptr, flags, perm); + return posixOpenC(path_with_null.ptr, flags, perm); } -pub fn posixOpenC(file_path: &const u8, flags: u32, perm: usize) %i32 { +pub fn posixOpenC(file_path: &const u8, flags: u32, perm: usize) !i32 { while (true) { const result = posix.open(file_path, flags, perm); const err = posix.getErrno(result); if (err > 0) { - return switch (err) { + switch (err) { posix.EINTR => continue, posix.EFAULT => unreachable, posix.EINVAL => unreachable, - posix.EACCES => error.AccessDenied, - posix.EFBIG, posix.EOVERFLOW => error.FileTooBig, - posix.EISDIR => error.IsDir, - posix.ELOOP => error.SymLinkLoop, - posix.EMFILE => error.ProcessFdQuotaExceeded, - posix.ENAMETOOLONG => error.NameTooLong, - posix.ENFILE => error.SystemFdQuotaExceeded, - posix.ENODEV => error.NoDevice, - posix.ENOENT => error.PathNotFound, - posix.ENOMEM => error.SystemResources, - posix.ENOSPC => error.NoSpaceLeft, - posix.ENOTDIR => error.NotDir, - posix.EPERM => error.AccessDenied, - posix.EEXIST => error.PathAlreadyExists, - else => unexpectedErrorPosix(err), - }; + posix.EACCES => return PosixOpenError.AccessDenied, + posix.EFBIG, posix.EOVERFLOW => return PosixOpenError.FileTooBig, + posix.EISDIR => return PosixOpenError.IsDir, + posix.ELOOP => return PosixOpenError.SymLinkLoop, + posix.EMFILE => return PosixOpenError.ProcessFdQuotaExceeded, + posix.ENAMETOOLONG => return PosixOpenError.NameTooLong, + posix.ENFILE => return PosixOpenError.SystemFdQuotaExceeded, + posix.ENODEV => return PosixOpenError.NoDevice, + posix.ENOENT => return PosixOpenError.PathNotFound, + posix.ENOMEM => return PosixOpenError.SystemResources, + posix.ENOSPC => return PosixOpenError.NoSpaceLeft, + posix.ENOTDIR => return PosixOpenError.NotDir, + posix.EPERM => return PosixOpenError.AccessDenied, + posix.EEXIST => return PosixOpenError.PathAlreadyExists, + else => return unexpectedErrorPosix(err), + } } return i32(result); } } -pub fn posixDup2(old_fd: i32, new_fd: i32) %void { +pub fn posixDup2(old_fd: i32, new_fd: i32) !void { while (true) { const err = posix.getErrno(posix.dup2(old_fd, new_fd)); if (err > 0) { @@ -328,7 +326,7 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) %void { } } -pub fn createNullDelimitedEnvMap(allocator: &Allocator, env_map: &const BufMap) %[]?&u8 { +pub fn createNullDelimitedEnvMap(allocator: &Allocator, env_map: &const BufMap) ![]?&u8 { const envp_count = env_map.count(); const envp_buf = try allocator.alloc(?&u8, envp_count + 1); mem.set(?&u8, envp_buf, null); @@ -365,7 +363,7 @@ pub fn freeNullDelimitedEnvMap(allocator: &Allocator, envp_buf: []?&u8) void { /// `argv[0]` is the executable path. /// This function also uses the PATH environment variable to get the full path to the executable. pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap, - allocator: &Allocator) %void + allocator: &Allocator) !void { const argv_buf = try allocator.alloc(?&u8, argv.len + 1); mem.set(?&u8, argv_buf, null); @@ -421,7 +419,19 @@ pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap, return posixExecveErrnoToErr(err); } -fn posixExecveErrnoToErr(err: usize) error { +pub const PosixExecveError = error { + SystemResources, + AccessDenied, + InvalidExe, + FileSystem, + IsDir, + FileNotFound, + NotDir, + FileBusy, + Unexpected, +}; + +fn posixExecveErrnoToErr(err: usize) PosixExecveError { assert(err > 0); return switch (err) { posix.EFAULT => unreachable, @@ -440,7 +450,7 @@ fn posixExecveErrnoToErr(err: usize) error { pub var posix_environ_raw: []&u8 = undefined; /// Caller must free result when done. -pub fn getEnvMap(allocator: &Allocator) %BufMap { +pub fn getEnvMap(allocator: &Allocator) !BufMap { var result = BufMap.init(allocator); errdefer result.deinit(); @@ -501,10 +511,8 @@ pub fn getEnvPosix(key: []const u8) ?[]const u8 { return null; } -error EnvironmentVariableNotFound; - /// Caller must free returned memory. -pub fn getEnvVarOwned(allocator: &mem.Allocator, key: []const u8) %[]u8 { +pub fn getEnvVarOwned(allocator: &mem.Allocator, key: []const u8) ![]u8 { if (is_windows) { const key_with_null = try cstr.addNullByte(allocator, key); defer allocator.free(key_with_null); @@ -538,7 +546,7 @@ pub fn getEnvVarOwned(allocator: &mem.Allocator, key: []const u8) %[]u8 { } /// Caller must free the returned memory. -pub fn getCwd(allocator: &Allocator) %[]u8 { +pub fn getCwd(allocator: &Allocator) ![]u8 { switch (builtin.os) { Os.windows => { var buf = try allocator.alloc(u8, 256); @@ -585,7 +593,9 @@ test "os.getCwd" { _ = getCwd(debug.global_allocator); } -pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) %void { +pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError; + +pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) SymLinkError!void { if (is_windows) { return symLinkWindows(allocator, existing_path, new_path); } else { @@ -593,7 +603,12 @@ pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []con } } -pub fn symLinkWindows(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) %void { +pub const WindowsSymLinkError = error { + OutOfMemory, + Unexpected, +}; + +pub fn symLinkWindows(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) WindowsSymLinkError!void { const existing_with_null = try cstr.addNullByte(allocator, existing_path); defer allocator.free(existing_with_null); const new_with_null = try cstr.addNullByte(allocator, new_path); @@ -607,7 +622,23 @@ pub fn symLinkWindows(allocator: &Allocator, existing_path: []const u8, new_path } } -pub fn symLinkPosix(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) %void { +pub const PosixSymLinkError = error { + OutOfMemory, + AccessDenied, + DiskQuota, + PathAlreadyExists, + FileSystem, + SymLinkLoop, + NameTooLong, + FileNotFound, + SystemResources, + NoSpaceLeft, + ReadOnlyFileSystem, + NotDir, + Unexpected, +}; + +pub fn symLinkPosix(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) PosixSymLinkError!void { const full_buf = try allocator.alloc(u8, existing_path.len + new_path.len + 2); defer allocator.free(full_buf); @@ -644,36 +675,36 @@ const b64_fs_encoder = base64.Base64Encoder.init( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char); -pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) %void { +pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) !void { if (symLink(allocator, existing_path, new_path)) { return; - } else |err| { - if (err != error.PathAlreadyExists) { - return err; - } + } else |err| switch (err) { + error.PathAlreadyExists => {}, + else => return err, // TODO zig should know this set does not include PathAlreadyExists } + const dirname = os.path.dirname(new_path); + var rand_buf: [12]u8 = undefined; - const tmp_path = try allocator.alloc(u8, new_path.len + base64.Base64Encoder.calcSize(rand_buf.len)); + const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len)); defer allocator.free(tmp_path); - mem.copy(u8, tmp_path[0..], new_path); + mem.copy(u8, tmp_path[0..], dirname); + tmp_path[dirname.len] = os.path.sep; while (true) { try getRandomBytes(rand_buf[0..]); - b64_fs_encoder.encode(tmp_path[new_path.len..], rand_buf); + b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf); + if (symLink(allocator, existing_path, tmp_path)) { return rename(allocator, tmp_path, new_path); - } else |err| { - if (err == error.PathAlreadyExists) { - continue; - } else { - return err; - } + } else |err| switch (err) { + error.PathAlreadyExists => continue, + else => return err, // TODO zig should know this set does not include PathAlreadyExists } } } -pub fn deleteFile(allocator: &Allocator, file_path: []const u8) %void { +pub fn deleteFile(allocator: &Allocator, file_path: []const u8) !void { if (builtin.os == Os.windows) { return deleteFileWindows(allocator, file_path); } else { @@ -681,10 +712,7 @@ pub fn deleteFile(allocator: &Allocator, file_path: []const u8) %void { } } -error FileNotFound; -error AccessDenied; - -pub fn deleteFileWindows(allocator: &Allocator, file_path: []const u8) %void { +pub fn deleteFileWindows(allocator: &Allocator, file_path: []const u8) !void { const buf = try allocator.alloc(u8, file_path.len + 1); defer allocator.free(buf); @@ -702,7 +730,7 @@ pub fn deleteFileWindows(allocator: &Allocator, file_path: []const u8) %void { } } -pub fn deleteFilePosix(allocator: &Allocator, file_path: []const u8) %void { +pub fn deleteFilePosix(allocator: &Allocator, file_path: []const u8) !void { const buf = try allocator.alloc(u8, file_path.len + 1); defer allocator.free(buf); @@ -728,38 +756,109 @@ pub fn deleteFilePosix(allocator: &Allocator, file_path: []const u8) %void { } } -/// Calls ::copyFileMode with 0o666 for the mode. -pub fn copyFile(allocator: &Allocator, source_path: []const u8, dest_path: []const u8) %void { - return copyFileMode(allocator, source_path, dest_path, 0o666); -} - -// TODO instead of accepting a mode argument, use the mode from fstat'ing the source path once open -/// Guaranteed to be atomic. -pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: []const u8, mode: usize) %void { - var rand_buf: [12]u8 = undefined; - const tmp_path = try allocator.alloc(u8, dest_path.len + base64.Base64Encoder.calcSize(rand_buf.len)); - defer allocator.free(tmp_path); - mem.copy(u8, tmp_path[0..], dest_path); - try getRandomBytes(rand_buf[0..]); - b64_fs_encoder.encode(tmp_path[dest_path.len..], rand_buf); - - var out_file = try io.File.openWriteMode(tmp_path, mode, allocator); - defer out_file.close(); - errdefer _ = deleteFile(allocator, tmp_path); - - var in_file = try io.File.openRead(source_path, allocator); +/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is +/// merged and readily available, +/// there is a possibility of power loss or application termination leaving temporary files present +/// in the same directory as dest_path. +/// Destination file will have the same mode as the source file. +pub fn copyFile(allocator: &Allocator, source_path: []const u8, dest_path: []const u8) !void { + var in_file = try os.File.openRead(allocator, source_path); defer in_file.close(); + const mode = try in_file.mode(); + + var atomic_file = try AtomicFile.init(allocator, dest_path, mode); + defer atomic_file.deinit(); + var buf: [page_size]u8 = undefined; while (true) { const amt = try in_file.read(buf[0..]); - try out_file.write(buf[0..amt]); - if (amt != buf.len) - return rename(allocator, tmp_path, dest_path); + try atomic_file.file.write(buf[0..amt]); + if (amt != buf.len) { + return atomic_file.finish(); + } } } -pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) %void { +/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is +/// merged and readily available, +/// there is a possibility of power loss or application termination leaving temporary files present +pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: []const u8, mode: FileMode) !void { + var in_file = try os.File.openRead(allocator, source_path); + defer in_file.close(); + + var atomic_file = try AtomicFile.init(allocator, dest_path, mode); + defer atomic_file.deinit(); + + var buf: [page_size]u8 = undefined; + while (true) { + const amt = try in_file.read(buf[0..]); + try atomic_file.file.write(buf[0..amt]); + if (amt != buf.len) { + return atomic_file.finish(); + } + } +} + +pub const AtomicFile = struct { + allocator: &Allocator, + file: os.File, + tmp_path: []u8, + dest_path: []const u8, + finished: bool, + + /// dest_path must remain valid for the lifetime of AtomicFile + /// call finish to atomically replace dest_path with contents + pub fn init(allocator: &Allocator, dest_path: []const u8, mode: FileMode) !AtomicFile { + const dirname = os.path.dirname(dest_path); + + var rand_buf: [12]u8 = undefined; + const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len)); + errdefer allocator.free(tmp_path); + mem.copy(u8, tmp_path[0..], dirname); + tmp_path[dirname.len] = os.path.sep; + + while (true) { + try getRandomBytes(rand_buf[0..]); + b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf); + + const file = os.File.openWriteNoClobber(allocator, tmp_path, mode) catch |err| switch (err) { + error.PathAlreadyExists => continue, + // TODO zig should figure out that this error set does not include PathAlreadyExists since + // it is handled in the above switch + else => return err, + }; + + return AtomicFile { + .allocator = allocator, + .file = file, + .tmp_path = tmp_path, + .dest_path = dest_path, + .finished = false, + }; + } + } + + /// always call deinit, even after successful finish() + pub fn deinit(self: &AtomicFile) void { + if (!self.finished) { + self.file.close(); + deleteFile(self.allocator, self.tmp_path) catch {}; + self.allocator.free(self.tmp_path); + self.finished = true; + } + } + + pub fn finish(self: &AtomicFile) !void { + assert(!self.finished); + self.file.close(); + try rename(self.allocator, self.tmp_path, self.dest_path); + self.allocator.free(self.tmp_path); + self.finished = true; + } +}; + +pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) !void { const full_buf = try allocator.alloc(u8, old_path.len + new_path.len + 2); defer allocator.free(full_buf); @@ -804,7 +903,7 @@ pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) } } -pub fn makeDir(allocator: &Allocator, dir_path: []const u8) %void { +pub fn makeDir(allocator: &Allocator, dir_path: []const u8) !void { if (is_windows) { return makeDirWindows(allocator, dir_path); } else { @@ -812,7 +911,7 @@ pub fn makeDir(allocator: &Allocator, dir_path: []const u8) %void { } } -pub fn makeDirWindows(allocator: &Allocator, dir_path: []const u8) %void { +pub fn makeDirWindows(allocator: &Allocator, dir_path: []const u8) !void { const path_buf = try cstr.addNullByte(allocator, dir_path); defer allocator.free(path_buf); @@ -826,7 +925,7 @@ pub fn makeDirWindows(allocator: &Allocator, dir_path: []const u8) %void { } } -pub fn makeDirPosix(allocator: &Allocator, dir_path: []const u8) %void { +pub fn makeDirPosix(allocator: &Allocator, dir_path: []const u8) !void { const path_buf = try cstr.addNullByte(allocator, dir_path); defer allocator.free(path_buf); @@ -852,7 +951,7 @@ pub fn makeDirPosix(allocator: &Allocator, dir_path: []const u8) %void { /// Calls makeDir recursively to make an entire path. Returns success if the path /// already exists and is a directory. -pub fn makePath(allocator: &Allocator, full_path: []const u8) %void { +pub fn makePath(allocator: &Allocator, full_path: []const u8) !void { const resolved_path = try path.resolve(allocator, full_path); defer allocator.free(resolved_path); @@ -890,7 +989,7 @@ pub fn makePath(allocator: &Allocator, full_path: []const u8) %void { /// Returns ::error.DirNotEmpty if the directory is not empty. /// To delete a directory recursively, see ::deleteTree -pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) %void { +pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) !void { const path_buf = try allocator.alloc(u8, dir_path.len + 1); defer allocator.free(path_buf); @@ -919,24 +1018,68 @@ pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) %void { /// removes it. If it cannot be removed because it is a non-empty directory, /// this function recursively removes its entries and then tries again. // TODO non-recursive implementation -pub fn deleteTree(allocator: &Allocator, full_path: []const u8) %void { +const DeleteTreeError = error { + OutOfMemory, + AccessDenied, + FileTooBig, + IsDir, + SymLinkLoop, + ProcessFdQuotaExceeded, + NameTooLong, + SystemFdQuotaExceeded, + NoDevice, + PathNotFound, + SystemResources, + NoSpaceLeft, + PathAlreadyExists, + ReadOnlyFileSystem, + NotDir, + FileNotFound, + FileSystem, + FileBusy, + DirNotEmpty, + Unexpected, +}; +pub fn deleteTree(allocator: &Allocator, full_path: []const u8) DeleteTreeError!void { start_over: while (true) { // First, try deleting the item as a file. This way we don't follow sym links. if (deleteFile(allocator, full_path)) { return; - } else |err| { - if (err == error.FileNotFound) - return; - if (err != error.IsDir) - return err; + } else |err| switch (err) { + error.FileNotFound => return, + error.IsDir => {}, + + error.OutOfMemory, + error.AccessDenied, + error.SymLinkLoop, + error.NameTooLong, + error.SystemResources, + error.ReadOnlyFileSystem, + error.NotDir, + error.FileSystem, + error.FileBusy, + error.Unexpected + => return err, } { - var dir = Dir.open(allocator, full_path) catch |err| { - if (err == error.FileNotFound) - return; - if (err == error.NotDir) - continue :start_over; - return err; + var dir = Dir.open(allocator, full_path) catch |err| switch (err) { + error.NotDir => continue :start_over, + + error.OutOfMemory, + error.AccessDenied, + error.FileTooBig, + error.IsDir, + error.SymLinkLoop, + error.ProcessFdQuotaExceeded, + error.NameTooLong, + error.SystemFdQuotaExceeded, + error.NoDevice, + error.PathNotFound, + error.SystemResources, + error.NoSpaceLeft, + error.PathAlreadyExists, + error.Unexpected + => return err, }; defer dir.close(); @@ -988,8 +1131,8 @@ pub const Dir = struct { }; }; - pub fn open(allocator: &Allocator, dir_path: []const u8) %Dir { - const fd = try posixOpen(dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0, allocator); + pub fn open(allocator: &Allocator, dir_path: []const u8) !Dir { + const fd = try posixOpen(allocator, dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0); return Dir { .allocator = allocator, .fd = fd, @@ -1006,7 +1149,7 @@ pub const Dir = struct { /// Memory such as file names referenced in this returned entry becomes invalid /// with subsequent calls to next, as well as when this ::Dir is deinitialized. - pub fn next(self: &Dir) %?Entry { + pub fn next(self: &Dir) !?Entry { start_over: while (true) { if (self.index >= self.end_index) { if (self.buf.len == 0) { @@ -1063,7 +1206,7 @@ pub const Dir = struct { } }; -pub fn changeCurDir(allocator: &Allocator, dir_path: []const u8) %void { +pub fn changeCurDir(allocator: &Allocator, dir_path: []const u8) !void { const path_buf = try allocator.alloc(u8, dir_path.len + 1); defer allocator.free(path_buf); @@ -1087,7 +1230,7 @@ pub fn changeCurDir(allocator: &Allocator, dir_path: []const u8) %void { } /// Read value of a symbolic link. -pub fn readLink(allocator: &Allocator, pathname: []const u8) %[]u8 { +pub fn readLink(allocator: &Allocator, pathname: []const u8) ![]u8 { const path_buf = try allocator.alloc(u8, pathname.len + 1); defer allocator.free(path_buf); @@ -1164,11 +1307,7 @@ test "os.sleep" { sleep(0, 1); } -error ResourceLimitReached; -error InvalidUserId; -error PermissionDenied; - -pub fn posix_setuid(uid: u32) %void { +pub fn posix_setuid(uid: u32) !void { const err = posix.getErrno(posix.setuid(uid)); if (err == 0) return; return switch (err) { @@ -1179,7 +1318,7 @@ pub fn posix_setuid(uid: u32) %void { }; } -pub fn posix_setreuid(ruid: u32, euid: u32) %void { +pub fn posix_setreuid(ruid: u32, euid: u32) !void { const err = posix.getErrno(posix.setreuid(ruid, euid)); if (err == 0) return; return switch (err) { @@ -1190,7 +1329,7 @@ pub fn posix_setreuid(ruid: u32, euid: u32) %void { }; } -pub fn posix_setgid(gid: u32) %void { +pub fn posix_setgid(gid: u32) !void { const err = posix.getErrno(posix.setgid(gid)); if (err == 0) return; return switch (err) { @@ -1201,7 +1340,7 @@ pub fn posix_setgid(gid: u32) %void { }; } -pub fn posix_setregid(rgid: u32, egid: u32) %void { +pub fn posix_setregid(rgid: u32, egid: u32) !void { const err = posix.getErrno(posix.setregid(rgid, egid)); if (err == 0) return; return switch (err) { @@ -1212,8 +1351,12 @@ pub fn posix_setregid(rgid: u32, egid: u32) %void { }; } -error NoStdHandles; -pub fn windowsGetStdHandle(handle_id: windows.DWORD) %windows.HANDLE { +pub const WindowsGetStdHandleErrs = error { + NoStdHandles, + Unexpected, +}; + +pub fn windowsGetStdHandle(handle_id: windows.DWORD) WindowsGetStdHandleErrs!windows.HANDLE { if (windows.GetStdHandle(handle_id)) |handle| { if (handle == windows.INVALID_HANDLE_VALUE) { const err = windows.GetLastError(); @@ -1267,6 +1410,8 @@ pub const ArgIteratorWindows = struct { quote_count: usize, seen_quote_count: usize, + pub const NextError = error{OutOfMemory}; + pub fn init() ArgIteratorWindows { return initWithCmdLine(windows.GetCommandLineA()); } @@ -1282,7 +1427,7 @@ pub const ArgIteratorWindows = struct { } /// You must free the returned memory when done. - pub fn next(self: &ArgIteratorWindows, allocator: &Allocator) ?%[]u8 { + pub fn next(self: &ArgIteratorWindows, allocator: &Allocator) ?(NextError![]u8) { // march forward over whitespace while (true) : (self.index += 1) { const byte = self.cmd_line[self.index]; @@ -1335,7 +1480,7 @@ pub const ArgIteratorWindows = struct { } } - fn internalNext(self: &ArgIteratorWindows, allocator: &Allocator) %[]u8 { + fn internalNext(self: &ArgIteratorWindows, allocator: &Allocator) NextError![]u8 { var buf = try Buffer.initSize(allocator, 0); defer buf.deinit(); @@ -1379,7 +1524,7 @@ pub const ArgIteratorWindows = struct { } } - fn emitBackslashes(self: &ArgIteratorWindows, buf: &Buffer, emit_count: usize) %void { + fn emitBackslashes(self: &ArgIteratorWindows, buf: &Buffer, emit_count: usize) !void { var i: usize = 0; while (i < emit_count) : (i += 1) { try buf.appendByte('\\'); @@ -1409,16 +1554,20 @@ pub const ArgIteratorWindows = struct { }; pub const ArgIterator = struct { - inner: if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix, + const InnerType = if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix; + + inner: InnerType, pub fn init() ArgIterator { return ArgIterator { - .inner = if (builtin.os == Os.windows) ArgIteratorWindows.init() else ArgIteratorPosix.init(), + .inner = InnerType.init(), }; } + + pub const NextError = ArgIteratorWindows.NextError; /// You must free the returned memory when done. - pub fn next(self: &ArgIterator, allocator: &Allocator) ?%[]u8 { + pub fn next(self: &ArgIterator, allocator: &Allocator) ?(NextError![]u8) { if (builtin.os == Os.windows) { return self.inner.next(allocator); } else { @@ -1443,7 +1592,7 @@ pub fn args() ArgIterator { } /// Caller must call freeArgs on result. -pub fn argsAlloc(allocator: &mem.Allocator) %[]const []u8 { +pub fn argsAlloc(allocator: &mem.Allocator) ![]const []u8 { // TODO refactor to only make 1 allocation. var it = args(); var contents = try Buffer.initSize(allocator, 0); @@ -1525,14 +1674,12 @@ test "std.os" { } -error Unexpected; - // TODO make this a build variable that you can set const unexpected_error_tracing = false; /// Call this when you made a syscall or something that sets errno /// and you get an unexpected error. -pub fn unexpectedErrorPosix(errno: usize) error { +pub fn unexpectedErrorPosix(errno: usize) (error{Unexpected}) { if (unexpected_error_tracing) { debug.warn("unexpected errno: {}\n", errno); debug.dumpStackTrace(); @@ -1542,7 +1689,7 @@ pub fn unexpectedErrorPosix(errno: usize) error { /// Call this when you made a windows DLL call or something that does SetLastError /// and you get an unexpected error. -pub fn unexpectedErrorWindows(err: windows.DWORD) error { +pub fn unexpectedErrorWindows(err: windows.DWORD) (error{Unexpected}) { if (unexpected_error_tracing) { debug.warn("unexpected GetLastError(): {}\n", err); debug.dumpStackTrace(); @@ -1550,25 +1697,38 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) error { return error.Unexpected; } -pub fn openSelfExe() %io.File { +pub fn openSelfExe() !os.File { switch (builtin.os) { Os.linux => { - return io.File.openRead("/proc/self/exe", null); + const proc_file_path = "/proc/self/exe"; + var fixed_buffer_mem: [proc_file_path.len + 1]u8 = undefined; + var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + return os.File.openRead(&fixed_allocator.allocator, proc_file_path); }, Os.macosx, Os.ios => { - @panic("TODO: openSelfExe on Darwin"); + var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined; + var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + const self_exe_path = try selfExePath(&fixed_allocator.allocator); + return os.File.openRead(&fixed_allocator.allocator, self_exe_path); }, else => @compileError("Unsupported OS"), } } +test "openSelfExe" { + switch (builtin.os) { + Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(), + else => return, // Unsupported OS. + } +} + /// Get the path to the current executable. /// If you only need the directory, use selfExeDirPath. /// If you only want an open file handle, use openSelfExe. /// This function may return an error if the current executable /// was deleted after spawning. /// Caller owns returned memory. -pub fn selfExePath(allocator: &mem.Allocator) %[]u8 { +pub fn selfExePath(allocator: &mem.Allocator) ![]u8 { switch (builtin.os) { Os.linux => { // If the currently executing binary has been deleted, @@ -1611,7 +1771,7 @@ pub fn selfExePath(allocator: &mem.Allocator) %[]u8 { /// Get the directory path that contains the current executable. /// Caller owns returned memory. -pub fn selfExeDirPath(allocator: &mem.Allocator) %[]u8 { +pub fn selfExeDirPath(allocator: &mem.Allocator) ![]u8 { switch (builtin.os) { Os.linux => { // If the currently executing binary has been deleted, diff --git a/std/os/linux/index.zig b/std/os/linux/index.zig index 43d175b74d..113f2ef454 100644 --- a/std/os/linux/index.zig +++ b/std/os/linux/index.zig @@ -720,7 +720,7 @@ pub fn accept4(fd: i32, noalias addr: &sockaddr, noalias len: &socklen_t, flags: // error SystemResources; // error Io; // -// pub fn if_nametoindex(name: []u8) %u32 { +// pub fn if_nametoindex(name: []u8) !u32 { // var ifr: ifreq = undefined; // // if (name.len >= ifr.ifr_name.len) { diff --git a/std/os/path.zig b/std/os/path.zig index eb95f83f45..0ea5d5a753 100644 --- a/std/os/path.zig +++ b/std/os/path.zig @@ -32,7 +32,7 @@ pub fn isSep(byte: u8) bool { /// Naively combines a series of paths with the native path seperator. /// Allocates memory for the result, which must be freed by the caller. -pub fn join(allocator: &Allocator, paths: ...) %[]u8 { +pub fn join(allocator: &Allocator, paths: ...) ![]u8 { if (is_windows) { return joinWindows(allocator, paths); } else { @@ -40,11 +40,11 @@ pub fn join(allocator: &Allocator, paths: ...) %[]u8 { } } -pub fn joinWindows(allocator: &Allocator, paths: ...) %[]u8 { +pub fn joinWindows(allocator: &Allocator, paths: ...) ![]u8 { return mem.join(allocator, sep_windows, paths); } -pub fn joinPosix(allocator: &Allocator, paths: ...) %[]u8 { +pub fn joinPosix(allocator: &Allocator, paths: ...) ![]u8 { return mem.join(allocator, sep_posix, paths); } @@ -313,7 +313,7 @@ fn asciiEqlIgnoreCase(s1: []const u8, s2: []const u8) bool { } /// Converts the command line arguments into a slice and calls `resolveSlice`. -pub fn resolve(allocator: &Allocator, args: ...) %[]u8 { +pub fn resolve(allocator: &Allocator, args: ...) ![]u8 { var paths: [args.len][]const u8 = undefined; comptime var arg_i = 0; inline while (arg_i < args.len) : (arg_i += 1) { @@ -323,7 +323,7 @@ pub fn resolve(allocator: &Allocator, args: ...) %[]u8 { } /// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`. -pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) %[]u8 { +pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) ![]u8 { if (is_windows) { return resolveWindows(allocator, paths); } else { @@ -337,7 +337,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) %[]u8 { /// If all paths are relative it uses the current working directory as a starting point. /// Each drive has its own current working directory. /// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters. -pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) %[]u8 { +pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) ![]u8 { if (paths.len == 0) { assert(is_windows); // resolveWindows called on non windows can't use getCwd return os.getCwd(allocator); @@ -520,7 +520,7 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) %[]u8 { /// It resolves "." and "..". /// The result does not have a trailing path separator. /// If all paths are relative it uses the current working directory as a starting point. -pub fn resolvePosix(allocator: &Allocator, paths: []const []const u8) %[]u8 { +pub fn resolvePosix(allocator: &Allocator, paths: []const []const u8) ![]u8 { if (paths.len == 0) { assert(!is_windows); // resolvePosix called on windows can't use getCwd return os.getCwd(allocator); @@ -890,7 +890,7 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) void { /// resolve to the same path (after calling `resolve` on each), a zero-length /// string is returned. /// On Windows this canonicalizes the drive to a capital letter and paths to `\\`. -pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) %[]u8 { +pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) ![]u8 { if (is_windows) { return relativeWindows(allocator, from, to); } else { @@ -898,7 +898,7 @@ pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) %[]u8 { } } -pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8) %[]u8 { +pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8) ![]u8 { const resolved_from = try resolveWindows(allocator, [][]const u8{from}); defer allocator.free(resolved_from); @@ -971,7 +971,7 @@ pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8) return []u8{}; } -pub fn relativePosix(allocator: &Allocator, from: []const u8, to: []const u8) %[]u8 { +pub fn relativePosix(allocator: &Allocator, from: []const u8, to: []const u8) ![]u8 { const resolved_from = try resolvePosix(allocator, [][]const u8{from}); defer allocator.free(resolved_from); @@ -1066,18 +1066,11 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons assert(mem.eql(u8, result, expected_output)); } -error AccessDenied; -error FileNotFound; -error NotSupported; -error NotDir; -error NameTooLong; -error SymLinkLoop; -error InputOutput; /// Return the canonicalized absolute pathname. /// Expands all symbolic links and resolves references to `.`, `..`, and /// extra `/` characters in ::pathname. /// Caller must deallocate result. -pub fn real(allocator: &Allocator, pathname: []const u8) %[]u8 { +pub fn real(allocator: &Allocator, pathname: []const u8) ![]u8 { switch (builtin.os) { Os.windows => { const pathname_buf = try allocator.alloc(u8, pathname.len + 1); @@ -1168,7 +1161,7 @@ pub fn real(allocator: &Allocator, pathname: []const u8) %[]u8 { return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr)); }, Os.linux => { - const fd = try os.posixOpen(pathname, posix.O_PATH|posix.O_NONBLOCK|posix.O_CLOEXEC, 0, allocator); + const fd = try os.posixOpen(allocator, pathname, posix.O_PATH|posix.O_NONBLOCK|posix.O_CLOEXEC, 0); defer os.close(fd); var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined; diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig index a8a2e3fcd5..5af318b7b0 100644 --- a/std/os/windows/util.zig +++ b/std/os/windows/util.zig @@ -1,4 +1,5 @@ const std = @import("../../index.zig"); +const builtin = @import("builtin"); const os = std.os; const windows = std.os.windows; const assert = std.debug.assert; @@ -6,11 +7,13 @@ const mem = std.mem; const BufMap = std.BufMap; const cstr = std.cstr; -error WaitAbandoned; -error WaitTimeOut; -error Unexpected; +pub const WaitError = error { + WaitAbandoned, + WaitTimeOut, + Unexpected, +}; -pub fn windowsWaitSingle(handle: windows.HANDLE, milliseconds: windows.DWORD) %void { +pub fn windowsWaitSingle(handle: windows.HANDLE, milliseconds: windows.DWORD) WaitError!void { const result = windows.WaitForSingleObject(handle, milliseconds); return switch (result) { windows.WAIT_ABANDONED => error.WaitAbandoned, @@ -30,21 +33,24 @@ pub fn windowsClose(handle: windows.HANDLE) void { assert(windows.CloseHandle(handle) != 0); } -error SystemResources; -error OperationAborted; -error IoPending; -error BrokenPipe; +pub const WriteError = error { + SystemResources, + OperationAborted, + IoPending, + BrokenPipe, + Unexpected, +}; -pub fn windowsWrite(handle: windows.HANDLE, bytes: []const u8) %void { +pub fn windowsWrite(handle: windows.HANDLE, bytes: []const u8) WriteError!void { if (windows.WriteFile(handle, @ptrCast(&const c_void, bytes.ptr), u32(bytes.len), null, null) == 0) { const err = windows.GetLastError(); return switch (err) { - windows.ERROR.INVALID_USER_BUFFER => error.SystemResources, - windows.ERROR.NOT_ENOUGH_MEMORY => error.SystemResources, - windows.ERROR.OPERATION_ABORTED => error.OperationAborted, - windows.ERROR.NOT_ENOUGH_QUOTA => error.SystemResources, - windows.ERROR.IO_PENDING => error.IoPending, - windows.ERROR.BROKEN_PIPE => error.BrokenPipe, + windows.ERROR.INVALID_USER_BUFFER => WriteError.SystemResources, + windows.ERROR.NOT_ENOUGH_MEMORY => WriteError.SystemResources, + windows.ERROR.OPERATION_ABORTED => WriteError.OperationAborted, + windows.ERROR.NOT_ENOUGH_QUOTA => WriteError.SystemResources, + windows.ERROR.IO_PENDING => WriteError.IoPending, + windows.ERROR.BROKEN_PIPE => WriteError.BrokenPipe, else => os.unexpectedErrorWindows(err), }; } @@ -75,43 +81,35 @@ pub fn windowsIsCygwinPty(handle: windows.HANDLE) bool { mem.indexOf(u16, name_wide, []u16{'-','p','t','y'}) != null; } -error SharingViolation; -error PipeBusy; +pub const OpenError = error { + SharingViolation, + PathAlreadyExists, + FileNotFound, + AccessDenied, + PipeBusy, + Unexpected, + OutOfMemory, +}; -/// `file_path` may need to be copied in memory to add a null terminating byte. In this case -/// a fixed size buffer of size ::max_noalloc_path_len is an attempted solution. If the fixed -/// size buffer is too small, and the provided allocator is null, ::error.NameTooLong is returned. -/// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory. -pub fn windowsOpen(file_path: []const u8, desired_access: windows.DWORD, share_mode: windows.DWORD, - creation_disposition: windows.DWORD, flags_and_attrs: windows.DWORD, allocator: ?&mem.Allocator) %windows.HANDLE +/// `file_path` needs to be copied in memory to add a null terminating byte, hence the allocator. +pub fn windowsOpen(allocator: &mem.Allocator, file_path: []const u8, desired_access: windows.DWORD, share_mode: windows.DWORD, + creation_disposition: windows.DWORD, flags_and_attrs: windows.DWORD) + OpenError!windows.HANDLE { - var stack_buf: [os.max_noalloc_path_len]u8 = undefined; - var path0: []u8 = undefined; - var need_free = false; - defer if (need_free) (??allocator).free(path0); + const path_with_null = try cstr.addNullByte(allocator, file_path); + defer allocator.free(path_with_null); - if (file_path.len < stack_buf.len) { - path0 = stack_buf[0..file_path.len + 1]; - } else if (allocator) |a| { - path0 = try a.alloc(u8, file_path.len + 1); - need_free = true; - } else { - return error.NameTooLong; - } - mem.copy(u8, path0, file_path); - path0[file_path.len] = 0; - - const result = windows.CreateFileA(path0.ptr, desired_access, share_mode, null, creation_disposition, + const result = windows.CreateFileA(path_with_null.ptr, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null); if (result == windows.INVALID_HANDLE_VALUE) { const err = windows.GetLastError(); return switch (err) { - windows.ERROR.SHARING_VIOLATION => error.SharingViolation, - windows.ERROR.ALREADY_EXISTS, windows.ERROR.FILE_EXISTS => error.PathAlreadyExists, - windows.ERROR.FILE_NOT_FOUND => error.FileNotFound, - windows.ERROR.ACCESS_DENIED => error.AccessDenied, - windows.ERROR.PIPE_BUSY => error.PipeBusy, + windows.ERROR.SHARING_VIOLATION => OpenError.SharingViolation, + windows.ERROR.ALREADY_EXISTS, windows.ERROR.FILE_EXISTS => OpenError.PathAlreadyExists, + windows.ERROR.FILE_NOT_FOUND => OpenError.FileNotFound, + windows.ERROR.ACCESS_DENIED => OpenError.AccessDenied, + windows.ERROR.PIPE_BUSY => OpenError.PipeBusy, else => os.unexpectedErrorWindows(err), }; } @@ -120,7 +118,7 @@ pub fn windowsOpen(file_path: []const u8, desired_access: windows.DWORD, share_m } /// Caller must free result. -pub fn createWindowsEnvBlock(allocator: &mem.Allocator, env_map: &const BufMap) %[]u8 { +pub fn createWindowsEnvBlock(allocator: &mem.Allocator, env_map: &const BufMap) ![]u8 { // count bytes needed const bytes_needed = x: { var bytes_needed: usize = 1; // 1 for the final null byte @@ -151,8 +149,7 @@ pub fn createWindowsEnvBlock(allocator: &mem.Allocator, env_map: &const BufMap) return result; } -error DllNotFound; -pub fn windowsLoadDll(allocator: &mem.Allocator, dll_path: []const u8) %windows.HMODULE { +pub fn windowsLoadDll(allocator: &mem.Allocator, dll_path: []const u8) !windows.HMODULE { const padded_buff = try cstr.addNullByte(allocator, dll_path); defer allocator.free(padded_buff); return windows.LoadLibraryA(padded_buff.ptr) ?? error.DllNotFound; @@ -164,6 +161,8 @@ pub fn windowsUnloadDll(hModule: windows.HMODULE) void { test "InvalidDll" { + if (builtin.os != builtin.Os.windows) return; + const DllName = "asdf.dll"; const allocator = std.debug.global_allocator; const handle = os.windowsLoadDll(allocator, DllName) catch |err| { diff --git a/std/special/bootstrap.zig b/std/special/bootstrap.zig index bcb3456353..f5754638b0 100644 --- a/std/special/bootstrap.zig +++ b/std/special/bootstrap.zig @@ -77,7 +77,7 @@ fn callMain() u8 { }, builtin.TypeId.Int => { if (@typeOf(root.main).ReturnType.bit_count != 8) { - @compileError("expected return type of main to be 'u8', 'noreturn', 'void', or '%void'"); + @compileError("expected return type of main to be 'u8', 'noreturn', 'void', or '!void'"); } return root.main(); }, @@ -91,6 +91,6 @@ fn callMain() u8 { }; return 0; }, - else => @compileError("expected return type of main to be 'u8', 'noreturn', 'void', or '%void'"), + else => @compileError("expected return type of main to be 'u8', 'noreturn', 'void', or '!void'"), } } diff --git a/std/special/build_file_template.zig b/std/special/build_file_template.zig index 282759bedb..2edfdadf50 100644 --- a/std/special/build_file_template.zig +++ b/std/special/build_file_template.zig @@ -1,6 +1,6 @@ const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) %void { +pub fn build(b: &Builder) !void { const mode = b.standardReleaseOptions(); const exe = b.addExecutable("YOUR_NAME_HERE", "src/main.zig"); exe.setBuildMode(mode); diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig index e1648276aa..4805240db7 100644 --- a/std/special/build_runner.zig +++ b/std/special/build_runner.zig @@ -1,5 +1,6 @@ const root = @import("@build"); const std = @import("std"); +const builtin = @import("builtin"); const io = std.io; const fmt = std.fmt; const os = std.os; @@ -8,9 +9,7 @@ const mem = std.mem; const ArrayList = std.ArrayList; const warn = std.debug.warn; -error InvalidArgs; - -pub fn main() %void { +pub fn main() !void { var arg_it = os.args(); // TODO use a more general purpose allocator here @@ -45,14 +44,14 @@ pub fn main() %void { var stderr_file = io.getStdErr(); var stderr_file_stream: io.FileOutStream = undefined; - var stderr_stream: %&io.OutStream = if (stderr_file) |*f| x: { + var stderr_stream = if (stderr_file) |*f| x: { stderr_file_stream = io.FileOutStream.init(f); break :x &stderr_file_stream.stream; } else |err| err; var stdout_file = io.getStdOut(); var stdout_file_stream: io.FileOutStream = undefined; - var stdout_stream: %&io.OutStream = if (stdout_file) |*f| x: { + var stdout_stream = if (stdout_file) |*f| x: { stdout_file_stream = io.FileOutStream.init(f); break :x &stdout_file_stream.stream; } else |err| err; @@ -112,7 +111,7 @@ pub fn main() %void { } builder.setInstallPrefix(prefix); - try root.build(&builder); + try runBuild(&builder); if (builder.validateUserInputDidItFail()) return usageAndErr(&builder, true, try stderr_stream); @@ -125,11 +124,19 @@ pub fn main() %void { }; } -fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) %void { +fn runBuild(builder: &Builder) error!void { + switch (@typeId(@typeOf(root.build).ReturnType)) { + builtin.TypeId.Void => root.build(builder), + builtin.TypeId.ErrorUnion => try root.build(builder), + else => @compileError("expected return type of build to be 'void' or '!void'"), + } +} + +fn usage(builder: &Builder, already_ran_build: bool, out_stream: var) !void { // run the build script to collect the options if (!already_ran_build) { builder.setInstallPrefix(null); - try root.build(builder); + try runBuild(builder); } // This usage text has to be synchronized with src/main.cpp @@ -149,6 +156,7 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) \\ \\General Options: \\ --help Print this help and exit + \\ --init Generate a build.zig template \\ --verbose Print commands before executing them \\ --prefix [path] Override default install prefix \\ --search-prefix [path] Add a path to look for binaries, libraries, headers @@ -183,12 +191,14 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) ); } -fn usageAndErr(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) error { +fn usageAndErr(builder: &Builder, already_ran_build: bool, out_stream: var) error { usage(builder, already_ran_build, out_stream) catch {}; return error.InvalidArgs; } -fn unwrapArg(arg: %[]u8) %[]u8 { +const UnwrapArgError = error {OutOfMemory}; + +fn unwrapArg(arg: UnwrapArgError![]u8) UnwrapArgError![]u8 { return arg catch |err| { warn("Unable to parse command line: {}\n", err); return err; diff --git a/std/special/test_runner.zig b/std/special/test_runner.zig index 3284f740b0..76a54a5018 100644 --- a/std/special/test_runner.zig +++ b/std/special/test_runner.zig @@ -4,7 +4,7 @@ const builtin = @import("builtin"); const test_fn_list = builtin.__zig_test_fn_slice; const warn = std.debug.warn; -pub fn main() %void { +pub fn main() !void { for (test_fn_list) |test_fn, i| { warn("Test {}/{} {}...", i + 1, test_fn_list.len, test_fn.name); diff --git a/std/unicode.zig b/std/unicode.zig index 235ac4ceac..df62e9162f 100644 --- a/std/unicode.zig +++ b/std/unicode.zig @@ -1,11 +1,9 @@ const std = @import("./index.zig"); -error Utf8InvalidStartByte; - /// Given the first byte of a UTF-8 codepoint, /// returns a number 1-4 indicating the total length of the codepoint in bytes. /// If this byte does not match the form of a UTF-8 start byte, returns Utf8InvalidStartByte. -pub fn utf8ByteSequenceLength(first_byte: u8) %u3 { +pub fn utf8ByteSequenceLength(first_byte: u8) !u3 { if (first_byte < 0b10000000) return u3(1); if (first_byte & 0b11100000 == 0b11000000) return u3(2); if (first_byte & 0b11110000 == 0b11100000) return u3(3); @@ -13,16 +11,11 @@ pub fn utf8ByteSequenceLength(first_byte: u8) %u3 { return error.Utf8InvalidStartByte; } -error Utf8OverlongEncoding; -error Utf8ExpectedContinuation; -error Utf8EncodesSurrogateHalf; -error Utf8CodepointTooLarge; - /// Decodes the UTF-8 codepoint encoded in the given slice of bytes. /// bytes.len must be equal to utf8ByteSequenceLength(bytes[0]) catch unreachable. /// If you already know the length at comptime, you can call one of /// utf8Decode2,utf8Decode3,utf8Decode4 directly instead of this function. -pub fn utf8Decode(bytes: []const u8) %u32 { +pub fn utf8Decode(bytes: []const u8) !u32 { return switch (bytes.len) { 1 => u32(bytes[0]), 2 => utf8Decode2(bytes), @@ -31,7 +24,7 @@ pub fn utf8Decode(bytes: []const u8) %u32 { else => unreachable, }; } -pub fn utf8Decode2(bytes: []const u8) %u32 { +pub fn utf8Decode2(bytes: []const u8) !u32 { std.debug.assert(bytes.len == 2); std.debug.assert(bytes[0] & 0b11100000 == 0b11000000); var value: u32 = bytes[0] & 0b00011111; @@ -44,7 +37,7 @@ pub fn utf8Decode2(bytes: []const u8) %u32 { return value; } -pub fn utf8Decode3(bytes: []const u8) %u32 { +pub fn utf8Decode3(bytes: []const u8) !u32 { std.debug.assert(bytes.len == 3); std.debug.assert(bytes[0] & 0b11110000 == 0b11100000); var value: u32 = bytes[0] & 0b00001111; @@ -62,7 +55,7 @@ pub fn utf8Decode3(bytes: []const u8) %u32 { return value; } -pub fn utf8Decode4(bytes: []const u8) %u32 { +pub fn utf8Decode4(bytes: []const u8) !u32 { std.debug.assert(bytes.len == 4); std.debug.assert(bytes[0] & 0b11111000 == 0b11110000); var value: u32 = bytes[0] & 0b00000111; @@ -85,7 +78,6 @@ pub fn utf8Decode4(bytes: []const u8) %u32 { return value; } -error UnexpectedEof; test "valid utf8" { testValid("\x00", 0x0); testValid("\x20", 0x20); @@ -161,7 +153,7 @@ fn testValid(bytes: []const u8, expected_codepoint: u32) void { std.debug.assert((testDecode(bytes) catch unreachable) == expected_codepoint); } -fn testDecode(bytes: []const u8) %u32 { +fn testDecode(bytes: []const u8) !u32 { const length = try utf8ByteSequenceLength(bytes[0]); if (bytes.len < length) return error.UnexpectedEof; std.debug.assert(bytes.len == length); diff --git a/src-self-hosted/ast.zig b/std/zig/ast.zig similarity index 98% rename from src-self-hosted/ast.zig rename to std/zig/ast.zig index b63c0a347e..a966c0316e 100644 --- a/src-self-hosted/ast.zig +++ b/std/zig/ast.zig @@ -1,7 +1,7 @@ -const std = @import("std"); +const std = @import("../index.zig"); const assert = std.debug.assert; const ArrayList = std.ArrayList; -const Token = @import("tokenizer.zig").Token; +const Token = std.zig.Token; const mem = std.mem; pub const Node = struct { diff --git a/std/zig/index.zig b/std/zig/index.zig new file mode 100644 index 0000000000..32699935d9 --- /dev/null +++ b/std/zig/index.zig @@ -0,0 +1,11 @@ +const tokenizer = @import("tokenizer.zig"); +pub const Token = tokenizer.Token; +pub const Tokenizer = tokenizer.Tokenizer; +pub const Parser = @import("parser.zig").Parser; +pub const ast = @import("ast.zig"); + +test "std.zig tests" { + _ = @import("tokenizer.zig"); + _ = @import("parser.zig"); + _ = @import("ast.zig"); +} diff --git a/src-self-hosted/parser.zig b/std/zig/parser.zig similarity index 95% rename from src-self-hosted/parser.zig rename to std/zig/parser.zig index 550d29b2c4..601e91fe7f 100644 --- a/src-self-hosted/parser.zig +++ b/std/zig/parser.zig @@ -1,10 +1,10 @@ -const std = @import("std"); +const std = @import("../index.zig"); const assert = std.debug.assert; const ArrayList = std.ArrayList; const mem = std.mem; -const ast = @import("ast.zig"); -const Tokenizer = @import("tokenizer.zig").Tokenizer; -const Token = @import("tokenizer.zig").Token; +const ast = std.zig.ast; +const Tokenizer = std.zig.Tokenizer; +const Token = std.zig.Token; const builtin = @import("builtin"); const io = std.io; @@ -12,8 +12,6 @@ const io = std.io; // get rid of this const warn = std.debug.warn; -error ParseError; - pub const Parser = struct { allocator: &mem.Allocator, tokenizer: &Tokenizer, @@ -63,7 +61,7 @@ pub const Parser = struct { NullableField: &?&ast.Node, List: &ArrayList(&ast.Node), - pub fn store(self: &const DestPtr, value: &ast.Node) %void { + pub fn store(self: &const DestPtr, value: &ast.Node) !void { switch (*self) { DestPtr.Field => |ptr| *ptr = value, DestPtr.NullableField => |ptr| *ptr = value, @@ -99,7 +97,7 @@ pub const Parser = struct { /// Returns an AST tree, allocated with the parser's allocator. /// Result should be freed with `freeAst` when done. - pub fn parse(self: &Parser) %Tree { + pub fn parse(self: &Parser) !Tree { var stack = self.initUtilityArrayList(State); defer self.deinitUtilityArrayList(stack); @@ -135,7 +133,6 @@ pub const Parser = struct { Token.Id.Eof => return Tree {.root_node = root_node}, else => { self.putBackToken(token); - // TODO shouldn't need this cast stack.append(State { .TopLevelExtern = null }) catch unreachable; continue; }, @@ -544,7 +541,7 @@ pub const Parser = struct { } } - fn createRoot(self: &Parser) %&ast.NodeRoot { + fn createRoot(self: &Parser) !&ast.NodeRoot { const node = try self.allocator.create(ast.NodeRoot); *node = ast.NodeRoot { @@ -555,7 +552,7 @@ pub const Parser = struct { } fn createVarDecl(self: &Parser, visib_token: &const ?Token, mut_token: &const Token, comptime_token: &const ?Token, - extern_token: &const ?Token) %&ast.NodeVarDecl + extern_token: &const ?Token) !&ast.NodeVarDecl { const node = try self.allocator.create(ast.NodeVarDecl); @@ -577,7 +574,7 @@ pub const Parser = struct { } fn createFnProto(self: &Parser, fn_token: &const Token, extern_token: &const ?Token, - cc_token: &const ?Token, visib_token: &const ?Token, inline_token: &const ?Token) %&ast.NodeFnProto + cc_token: &const ?Token, visib_token: &const ?Token, inline_token: &const ?Token) !&ast.NodeFnProto { const node = try self.allocator.create(ast.NodeFnProto); @@ -599,7 +596,7 @@ pub const Parser = struct { return node; } - fn createParamDecl(self: &Parser) %&ast.NodeParamDecl { + fn createParamDecl(self: &Parser) !&ast.NodeParamDecl { const node = try self.allocator.create(ast.NodeParamDecl); *node = ast.NodeParamDecl { @@ -613,7 +610,7 @@ pub const Parser = struct { return node; } - fn createBlock(self: &Parser, begin_token: &const Token) %&ast.NodeBlock { + fn createBlock(self: &Parser, begin_token: &const Token) !&ast.NodeBlock { const node = try self.allocator.create(ast.NodeBlock); *node = ast.NodeBlock { @@ -625,7 +622,7 @@ pub const Parser = struct { return node; } - fn createInfixOp(self: &Parser, op_token: &const Token, op: &const ast.NodeInfixOp.InfixOp) %&ast.NodeInfixOp { + fn createInfixOp(self: &Parser, op_token: &const Token, op: &const ast.NodeInfixOp.InfixOp) !&ast.NodeInfixOp { const node = try self.allocator.create(ast.NodeInfixOp); *node = ast.NodeInfixOp { @@ -638,7 +635,7 @@ pub const Parser = struct { return node; } - fn createPrefixOp(self: &Parser, op_token: &const Token, op: &const ast.NodePrefixOp.PrefixOp) %&ast.NodePrefixOp { + fn createPrefixOp(self: &Parser, op_token: &const Token, op: &const ast.NodePrefixOp.PrefixOp) !&ast.NodePrefixOp { const node = try self.allocator.create(ast.NodePrefixOp); *node = ast.NodePrefixOp { @@ -650,7 +647,7 @@ pub const Parser = struct { return node; } - fn createIdentifier(self: &Parser, name_token: &const Token) %&ast.NodeIdentifier { + fn createIdentifier(self: &Parser, name_token: &const Token) !&ast.NodeIdentifier { const node = try self.allocator.create(ast.NodeIdentifier); *node = ast.NodeIdentifier { @@ -660,7 +657,7 @@ pub const Parser = struct { return node; } - fn createIntegerLiteral(self: &Parser, token: &const Token) %&ast.NodeIntegerLiteral { + fn createIntegerLiteral(self: &Parser, token: &const Token) !&ast.NodeIntegerLiteral { const node = try self.allocator.create(ast.NodeIntegerLiteral); *node = ast.NodeIntegerLiteral { @@ -670,7 +667,7 @@ pub const Parser = struct { return node; } - fn createFloatLiteral(self: &Parser, token: &const Token) %&ast.NodeFloatLiteral { + fn createFloatLiteral(self: &Parser, token: &const Token) !&ast.NodeFloatLiteral { const node = try self.allocator.create(ast.NodeFloatLiteral); *node = ast.NodeFloatLiteral { @@ -680,13 +677,13 @@ pub const Parser = struct { return node; } - fn createAttachIdentifier(self: &Parser, dest_ptr: &const DestPtr, name_token: &const Token) %&ast.NodeIdentifier { + fn createAttachIdentifier(self: &Parser, dest_ptr: &const DestPtr, name_token: &const Token) !&ast.NodeIdentifier { const node = try self.createIdentifier(name_token); try dest_ptr.store(&node.base); return node; } - fn createAttachParamDecl(self: &Parser, list: &ArrayList(&ast.Node)) %&ast.NodeParamDecl { + fn createAttachParamDecl(self: &Parser, list: &ArrayList(&ast.Node)) !&ast.NodeParamDecl { const node = try self.createParamDecl(); try list.append(&node.base); return node; @@ -694,7 +691,7 @@ pub const Parser = struct { fn createAttachFnProto(self: &Parser, list: &ArrayList(&ast.Node), fn_token: &const Token, extern_token: &const ?Token, cc_token: &const ?Token, visib_token: &const ?Token, - inline_token: &const ?Token) %&ast.NodeFnProto + inline_token: &const ?Token) !&ast.NodeFnProto { const node = try self.createFnProto(fn_token, extern_token, cc_token, visib_token, inline_token); try list.append(&node.base); @@ -702,14 +699,14 @@ pub const Parser = struct { } fn createAttachVarDecl(self: &Parser, list: &ArrayList(&ast.Node), visib_token: &const ?Token, - mut_token: &const Token, comptime_token: &const ?Token, extern_token: &const ?Token) %&ast.NodeVarDecl + mut_token: &const Token, comptime_token: &const ?Token, extern_token: &const ?Token) !&ast.NodeVarDecl { const node = try self.createVarDecl(visib_token, mut_token, comptime_token, extern_token); try list.append(&node.base); return node; } - fn parseError(self: &Parser, token: &const Token, comptime fmt: []const u8, args: ...) error { + fn parseError(self: &Parser, token: &const Token, comptime fmt: []const u8, args: ...) (error{ParseError}) { const loc = self.tokenizer.getTokenLocation(token); warn("{}:{}:{}: error: " ++ fmt ++ "\n", self.source_file_name, loc.line + 1, loc.column + 1, args); warn("{}\n", self.tokenizer.buffer[loc.line_start..loc.line_end]); @@ -730,13 +727,13 @@ pub const Parser = struct { return error.ParseError; } - fn expectToken(self: &Parser, token: &const Token, id: @TagType(Token.Id)) %void { + fn expectToken(self: &Parser, token: &const Token, id: @TagType(Token.Id)) !void { if (token.id != id) { return self.parseError(token, "expected {}, found {}", @tagName(id), @tagName(token.id)); } } - fn eatToken(self: &Parser, id: @TagType(Token.Id)) %Token { + fn eatToken(self: &Parser, id: @TagType(Token.Id)) !Token { const token = self.getNextToken(); try self.expectToken(token, id); return token; @@ -763,7 +760,7 @@ pub const Parser = struct { indent: usize, }; - pub fn renderAst(self: &Parser, stream: &std.io.OutStream, root_node: &ast.NodeRoot) %void { + pub fn renderAst(self: &Parser, stream: var, root_node: &ast.NodeRoot) !void { var stack = self.initUtilityArrayList(RenderAstFrame); defer self.deinitUtilityArrayList(stack); @@ -802,7 +799,7 @@ pub const Parser = struct { Indent: usize, }; - pub fn renderSource(self: &Parser, stream: &std.io.OutStream, root_node: &ast.NodeRoot) %void { + pub fn renderSource(self: &Parser, stream: var, root_node: &ast.NodeRoot) !void { var stack = self.initUtilityArrayList(RenderState); defer self.deinitUtilityArrayList(stack); @@ -1038,14 +1035,11 @@ pub const Parser = struct { var fixed_buffer_mem: [100 * 1024]u8 = undefined; -fn testParse(source: []const u8, allocator: &mem.Allocator) %[]u8 { +fn testParse(source: []const u8, allocator: &mem.Allocator) ![]u8 { var padded_source: [0x100]u8 = undefined; std.mem.copy(u8, padded_source[0..source.len], source); - padded_source[source.len + 0] = '\n'; - padded_source[source.len + 1] = '\n'; - padded_source[source.len + 2] = '\n'; - var tokenizer = Tokenizer.init(padded_source[0..source.len + 3]); + var tokenizer = Tokenizer.init(padded_source[0..source.len]); var parser = Parser.init(&tokenizer, allocator, "(memory buffer)"); defer parser.deinit(); @@ -1058,13 +1052,9 @@ fn testParse(source: []const u8, allocator: &mem.Allocator) %[]u8 { return buffer.toOwnedSlice(); } -error TestFailed; -error NondeterministicMemoryUsage; -error MemoryLeakDetected; - // TODO test for memory leaks // TODO test for valid frees -fn testCanonical(source: []const u8) %void { +fn testCanonical(source: []const u8) !void { const needed_alloc_count = x: { // Try it once with unlimited memory, make sure it works var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); @@ -1088,16 +1078,18 @@ fn testCanonical(source: []const u8) %void { var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, fail_index); if (testParse(source, &failing_allocator.allocator)) |_| { return error.NondeterministicMemoryUsage; - } else |err| { - assert(err == error.OutOfMemory); - // TODO make this pass - //if (failing_allocator.allocated_bytes != failing_allocator.freed_bytes) { - // warn("\nfail_index: {}/{}\nallocated bytes: {}\nfreed bytes: {}\nallocations: {}\ndeallocations: {}\n", - // fail_index, needed_alloc_count, - // failing_allocator.allocated_bytes, failing_allocator.freed_bytes, - // failing_allocator.index, failing_allocator.deallocations); - // return error.MemoryLeakDetected; - //} + } else |err| switch (err) { + error.OutOfMemory => { + // TODO make this pass + //if (failing_allocator.allocated_bytes != failing_allocator.freed_bytes) { + // warn("\nfail_index: {}/{}\nallocated bytes: {}\nfreed bytes: {}\nallocations: {}\ndeallocations: {}\n", + // fail_index, needed_alloc_count, + // failing_allocator.allocated_bytes, failing_allocator.freed_bytes, + // failing_allocator.index, failing_allocator.deallocations); + // return error.MemoryLeakDetected; + //} + }, + error.ParseError => @panic("test failed"), } } } diff --git a/src-self-hosted/tokenizer.zig b/std/zig/tokenizer.zig similarity index 97% rename from src-self-hosted/tokenizer.zig rename to std/zig/tokenizer.zig index f1f87abfc9..546356caa3 100644 --- a/src-self-hosted/tokenizer.zig +++ b/std/zig/tokenizer.zig @@ -1,4 +1,4 @@ -const std = @import("std"); +const std = @import("../index.zig"); const mem = std.mem; pub const Token = struct { @@ -175,12 +175,7 @@ pub const Tokenizer = struct { std.debug.warn("{} \"{}\"\n", @tagName(token.id), self.buffer[token.start..token.end]); } - /// buffer must end with "\n\n\n". This is so that attempting to decode - /// a the 3 trailing bytes of a 4-byte utf8 sequence is never a buffer overflow. pub fn init(buffer: []const u8) Tokenizer { - std.debug.assert(buffer[buffer.len - 1] == '\n'); - std.debug.assert(buffer[buffer.len - 2] == '\n'); - std.debug.assert(buffer[buffer.len - 3] == '\n'); return Tokenizer { .buffer = buffer, .index = 0, @@ -556,8 +551,9 @@ pub const Tokenizer = struct { } else { // check utf8-encoded character. const length = std.unicode.utf8ByteSequenceLength(c0) catch return 1; - // the last 3 bytes in the buffer are guaranteed to be '\n', - // which means we don't need to do any bounds checking here. + if (self.index + length >= self.buffer.len) { + return u3(self.buffer.len - self.index); + } const bytes = self.buffer[self.index..self.index + length]; switch (length) { 2 => { diff --git a/test/cases/cast.zig b/test/cases/cast.zig index 2455179c89..6ffb558174 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -32,7 +32,6 @@ fn funcWithConstPtrPtr(x: &const &i32) void { **x += 1; } -error ItBroke; test "explicit cast from integer to error type" { testCastIntToErr(error.ItBroke); comptime testCastIntToErr(error.ItBroke); @@ -75,7 +74,7 @@ test "string literal to &const []const u8" { assert(mem.eql(u8, *x, "hello")); } -test "implicitly cast from T to %?T" { +test "implicitly cast from T to error!?T" { castToMaybeTypeError(1); comptime castToMaybeTypeError(1); } @@ -84,37 +83,37 @@ const A = struct { }; fn castToMaybeTypeError(z: i32) void { const x = i32(1); - const y: %?i32 = x; + const y: error!?i32 = x; assert(??(try y) == 1); const f = z; - const g: %?i32 = f; + const g: error!?i32 = f; const a = A{ .a = z }; - const b: %?A = a; + const b: error!?A = a; assert((??(b catch unreachable)).a == 1); } -test "implicitly cast from int to %?T" { +test "implicitly cast from int to error!?T" { implicitIntLitToMaybe(); comptime implicitIntLitToMaybe(); } fn implicitIntLitToMaybe() void { const f: ?i32 = 1; - const g: %?i32 = 1; + const g: error!?i32 = 1; } -test "return null from fn() %?&T" { +test "return null from fn() error!?&T" { const a = returnNullFromMaybeTypeErrorRef(); const b = returnNullLitFromMaybeTypeErrorRef(); assert((try a) == null and (try b) == null); } -fn returnNullFromMaybeTypeErrorRef() %?&A { +fn returnNullFromMaybeTypeErrorRef() error!?&A { const a: ?&A = null; return a; } -fn returnNullLitFromMaybeTypeErrorRef() %?&A { +fn returnNullLitFromMaybeTypeErrorRef() error!?&A { return null; } @@ -161,7 +160,7 @@ fn castToMaybeSlice() ?[]const u8 { } -test "implicitly cast from [0]T to %[]T" { +test "implicitly cast from [0]T to error![]T" { testCastZeroArrayToErrSliceMut(); comptime testCastZeroArrayToErrSliceMut(); } @@ -170,11 +169,11 @@ fn testCastZeroArrayToErrSliceMut() void { assert((gimmeErrOrSlice() catch unreachable).len == 0); } -fn gimmeErrOrSlice() %[]u8 { +fn gimmeErrOrSlice() error![]u8 { return []u8{}; } -test "peer type resolution: [0]u8, []const u8, and %[]u8" { +test "peer type resolution: [0]u8, []const u8, and error![]u8" { { var data = "hi"; const slice = data[0..]; @@ -188,7 +187,7 @@ test "peer type resolution: [0]u8, []const u8, and %[]u8" { assert((try peerTypeEmptyArrayAndSliceAndError(false, slice)).len == 1); } } -fn peerTypeEmptyArrayAndSliceAndError(a: bool, slice: []u8) %[]u8 { +fn peerTypeEmptyArrayAndSliceAndError(a: bool, slice: []u8) error![]u8 { if (a) { return []u8{}; } @@ -230,7 +229,7 @@ fn foo(args: ...) void { test "peer type resolution: error and [N]T" { - // TODO: implicit %T to %U where T can implicitly cast to U + // TODO: implicit error!T to error!U where T can implicitly cast to U //assert(mem.eql(u8, try testPeerErrorAndArray(0), "OK")); //comptime assert(mem.eql(u8, try testPeerErrorAndArray(0), "OK")); @@ -238,14 +237,13 @@ test "peer type resolution: error and [N]T" { comptime assert(mem.eql(u8, try testPeerErrorAndArray2(1), "OKK")); } -error BadValue; -//fn testPeerErrorAndArray(x: u8) %[]const u8 { +//fn testPeerErrorAndArray(x: u8) error![]const u8 { // return switch (x) { // 0x00 => "OK", // else => error.BadValue, // }; //} -fn testPeerErrorAndArray2(x: u8) %[]const u8 { +fn testPeerErrorAndArray2(x: u8) error![]const u8 { return switch (x) { 0x00 => "OK", 0x01 => "OKK", diff --git a/test/cases/defer.zig b/test/cases/defer.zig index 6490ec0acd..a989af18c2 100644 --- a/test/cases/defer.zig +++ b/test/cases/defer.zig @@ -3,9 +3,7 @@ const assert = @import("std").debug.assert; var result: [3]u8 = undefined; var index: usize = undefined; -error FalseNotAllowed; - -fn runSomeErrorDefers(x: bool) %bool { +fn runSomeErrorDefers(x: bool) !bool { index = 0; defer {result[index] = 'a'; index += 1;} errdefer {result[index] = 'b'; index += 1;} diff --git a/test/cases/enum_with_members.zig b/test/cases/enum_with_members.zig index 9abc15d129..0c2ae1c383 100644 --- a/test/cases/enum_with_members.zig +++ b/test/cases/enum_with_members.zig @@ -6,7 +6,7 @@ const ET = union(enum) { SINT: i32, UINT: u32, - pub fn print(a: &const ET, buf: []u8) %usize { + pub fn print(a: &const ET, buf: []u8) error!usize { return switch (*a) { ET.SINT => |x| fmt.formatIntBuf(buf, x, 10, false, 0), ET.UINT => |x| fmt.formatIntBuf(buf, x, 10, false, 0), diff --git a/test/cases/error.zig b/test/cases/error.zig index 47e5f25be9..e64bf02c91 100644 --- a/test/cases/error.zig +++ b/test/cases/error.zig @@ -1,16 +1,18 @@ -const assert = @import("std").debug.assert; -const mem = @import("std").mem; +const std = @import("std"); +const assert = std.debug.assert; +const mem = std.mem; +const builtin = @import("builtin"); -pub fn foo() %i32 { +pub fn foo() error!i32 { const x = try bar(); return x + 1; } -pub fn bar() %i32 { +pub fn bar() error!i32 { return 13; } -pub fn baz() %i32 { +pub fn baz() error!i32 { const y = foo() catch 1234; return y + 1; } @@ -19,7 +21,6 @@ test "error wrapping" { assert((baz() catch unreachable) == 15); } -error ItBroke; fn gimmeItBroke() []const u8 { return @errorName(error.ItBroke); } @@ -28,8 +29,6 @@ test "@errorName" { assert(mem.eql(u8, @errorName(error.AnError), "AnError")); assert(mem.eql(u8, @errorName(error.ALongerErrorName), "ALongerErrorName")); } -error AnError; -error ALongerErrorName; test "error values" { @@ -37,16 +36,11 @@ test "error values" { const b = i32(error.err2); assert(a != b); } -error err1; -error err2; test "redefinition of error values allowed" { shouldBeNotEqual(error.AnError, error.SecondError); } -error AnError; -error AnError; -error SecondError; fn shouldBeNotEqual(a: error, b: error) void { if (a == b) unreachable; } @@ -58,8 +52,7 @@ test "error binary operator" { assert(a == 3); assert(b == 10); } -error ItBroke; -fn errBinaryOperatorG(x: bool) %isize { +fn errBinaryOperatorG(x: bool) error!isize { return if (x) error.ItBroke else isize(10); } @@ -68,18 +61,117 @@ test "unwrap simple value from error" { const i = unwrapSimpleValueFromErrorDo() catch unreachable; assert(i == 13); } -fn unwrapSimpleValueFromErrorDo() %isize { return 13; } +fn unwrapSimpleValueFromErrorDo() error!isize { return 13; } test "error return in assignment" { doErrReturnInAssignment() catch unreachable; } -fn doErrReturnInAssignment() %void { +fn doErrReturnInAssignment() error!void { var x : i32 = undefined; x = try makeANonErr(); } -fn makeANonErr() %i32 { +fn makeANonErr() error!i32 { return 1; } + +test "error union type " { + testErrorUnionType(); + comptime testErrorUnionType(); +} + +fn testErrorUnionType() void { + const x: error!i32 = 1234; + if (x) |value| assert(value == 1234) else |_| unreachable; + assert(@typeId(@typeOf(x)) == builtin.TypeId.ErrorUnion); + assert(@typeId(@typeOf(x).ErrorSet) == builtin.TypeId.ErrorSet); + assert(@typeOf(x).ErrorSet == error); +} + +test "error set type " { + testErrorSetType(); + comptime testErrorSetType(); +} + +const MyErrSet = error {OutOfMemory, FileNotFound}; + +fn testErrorSetType() void { + assert(@memberCount(MyErrSet) == 2); + + const a: MyErrSet!i32 = 5678; + const b: MyErrSet!i32 = MyErrSet.OutOfMemory; + + if (a) |value| assert(value == 5678) else |err| switch (err) { + error.OutOfMemory => unreachable, + error.FileNotFound => unreachable, + } +} + + +test "explicit error set cast" { + testExplicitErrorSetCast(Set1.A); + comptime testExplicitErrorSetCast(Set1.A); +} + +const Set1 = error{A, B}; +const Set2 = error{A, C}; + +fn testExplicitErrorSetCast(set1: Set1) void { + var x = Set2(set1); + var y = Set1(x); + assert(y == error.A); +} + +test "comptime test error for empty error set" { + testComptimeTestErrorEmptySet(1234); + comptime testComptimeTestErrorEmptySet(1234); +} + +const EmptyErrorSet = error {}; + +fn testComptimeTestErrorEmptySet(x: EmptyErrorSet!i32) void { + if (x) |v| assert(v == 1234) else |err| @compileError("bad"); +} + +test "syntax: nullable operator in front of error union operator" { + comptime { + assert(?error!i32 == ?(error!i32)); + } +} + +test "comptime err to int of error set with only 1 possible value" { + testErrToIntWithOnePossibleValue(error.A, u32(error.A)); + comptime testErrToIntWithOnePossibleValue(error.A, u32(error.A)); +} +fn testErrToIntWithOnePossibleValue(x: error{A}, comptime value: u32) void { + if (u32(x) != value) { + @compileError("bad"); + } +} + +test "error union peer type resolution" { + testErrorUnionPeerTypeResolution(1); + comptime testErrorUnionPeerTypeResolution(1); +} + +fn testErrorUnionPeerTypeResolution(x: i32) void { + const y = switch (x) { + 1 => bar_1(), + 2 => baz_1(), + else => quux_1(), + }; +} + +fn bar_1() error { + return error.A; +} + +fn baz_1() !i32 { + return error.B; +} + +fn quux_1() !i32 { + return error.C; +} diff --git a/test/cases/ir_block_deps.zig b/test/cases/ir_block_deps.zig index 44dfa330a1..202df19f62 100644 --- a/test/cases/ir_block_deps.zig +++ b/test/cases/ir_block_deps.zig @@ -1,6 +1,6 @@ const assert = @import("std").debug.assert; -fn foo(id: u64) %i32 { +fn foo(id: u64) !i32 { return switch (id) { 1 => getErrInt(), 2 => { @@ -11,9 +11,7 @@ fn foo(id: u64) %i32 { }; } -fn getErrInt() %i32 { return 0; } - -error ItBroke; +fn getErrInt() error!i32 { return 0; } test "ir block deps" { assert((foo(1) catch unreachable) == 0); diff --git a/test/cases/misc.zig b/test/cases/misc.zig index 85757efbb8..964c5babc1 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -262,7 +262,7 @@ test "generic malloc free" { memFree(u8, a); } const some_mem : [100]u8 = undefined; -fn memAlloc(comptime T: type, n: usize) %[]T { +fn memAlloc(comptime T: type, n: usize) error![]T { return @ptrCast(&T, &some_mem[0])[0..n]; } fn memFree(comptime T: type, memory: []T) void { } @@ -419,7 +419,7 @@ test "cast slice to u8 slice" { test "pointer to void return type" { testPointerToVoidReturnType() catch unreachable; } -fn testPointerToVoidReturnType() %void { +fn testPointerToVoidReturnType() error!void { const a = testPointerToVoidReturnType2(); return *a; } @@ -475,8 +475,8 @@ test "@typeId" { assert(@typeId(@typeOf(undefined)) == Tid.UndefinedLiteral); assert(@typeId(@typeOf(null)) == Tid.NullLiteral); assert(@typeId(?i32) == Tid.Nullable); - assert(@typeId(%i32) == Tid.ErrorUnion); - assert(@typeId(error) == Tid.Error); + assert(@typeId(error!i32) == Tid.ErrorUnion); + assert(@typeId(error) == Tid.ErrorSet); assert(@typeId(AnEnum) == Tid.Enum); assert(@typeId(@typeOf(AUnionEnum.One)) == Tid.Enum); assert(@typeId(AUnionEnum) == Tid.Union); diff --git a/test/cases/reflection.zig b/test/cases/reflection.zig index 8e103a3bc7..18a766d9fc 100644 --- a/test/cases/reflection.zig +++ b/test/cases/reflection.zig @@ -5,7 +5,7 @@ test "reflection: array, pointer, nullable, error union type child" { comptime { assert(([10]u8).Child == u8); assert((&u8).Child == u8); - assert((%u8).Child == u8); + assert((error!u8).Payload == u8); assert((?u8).Child == u8); } } diff --git a/test/cases/switch.zig b/test/cases/switch.zig index f742057e68..a0ac646160 100644 --- a/test/cases/switch.zig +++ b/test/cases/switch.zig @@ -225,7 +225,7 @@ fn switchWithUnreachable(x: i32) i32 { return 10; } -fn return_a_number() %i32 { +fn return_a_number() error!i32 { return 1; } diff --git a/test/cases/switch_prong_err_enum.zig b/test/cases/switch_prong_err_enum.zig index 2da5b5f09a..136e8834e6 100644 --- a/test/cases/switch_prong_err_enum.zig +++ b/test/cases/switch_prong_err_enum.zig @@ -2,19 +2,17 @@ const assert = @import("std").debug.assert; var read_count: u64 = 0; -fn readOnce() %u64 { +fn readOnce() error!u64 { read_count += 1; return read_count; } -error InvalidDebugInfo; - const FormValue = union(enum) { Address: u64, Other: bool, }; -fn doThing(form_id: u64) %FormValue { +fn doThing(form_id: u64) error!FormValue { return switch (form_id) { 17 => FormValue { .Address = try readOnce() }, else => error.InvalidDebugInfo, diff --git a/test/cases/switch_prong_implicit_cast.zig b/test/cases/switch_prong_implicit_cast.zig index 300b8c21a1..335feeef43 100644 --- a/test/cases/switch_prong_implicit_cast.zig +++ b/test/cases/switch_prong_implicit_cast.zig @@ -5,9 +5,7 @@ const FormValue = union(enum) { Two: bool, }; -error Whatever; - -fn foo(id: u64) %FormValue { +fn foo(id: u64) !FormValue { return switch (id) { 2 => FormValue { .Two = true }, 1 => FormValue { .One = {} }, diff --git a/test/cases/try.zig b/test/cases/try.zig index 00259721b1..4a0425e22e 100644 --- a/test/cases/try.zig +++ b/test/cases/try.zig @@ -17,10 +17,7 @@ fn tryOnErrorUnionImpl() void { assert(x == 11); } -error ItBroke; -error NoMem; -error CrappedOut; -fn returnsTen() %i32 { +fn returnsTen() error!i32 { return 10; } @@ -32,7 +29,7 @@ test "try without vars" { assert(result2 == 1); } -fn failIfTrue(ok: bool) %void { +fn failIfTrue(ok: bool) error!void { if (ok) { return error.ItBroke; } else { diff --git a/test/cases/union.zig b/test/cases/union.zig index ea8a9da188..dc2a7c3414 100644 --- a/test/cases/union.zig +++ b/test/cases/union.zig @@ -13,7 +13,7 @@ const Agg = struct { const v1 = Value { .Int = 1234 }; const v2 = Value { .Array = []u8{3} ** 9 }; -const err = (%Agg)(Agg { +const err = (error!Agg)(Agg { .val1 = v1, .val2 = v2, }); diff --git a/test/cases/while.zig b/test/cases/while.zig index 5970e742ea..33d5a5623a 100644 --- a/test/cases/while.zig +++ b/test/cases/while.zig @@ -50,7 +50,7 @@ fn runContinueAndBreakTest() void { test "return with implicit cast from while loop" { returnWithImplicitCastFromWhileLoopTest() catch unreachable; } -fn returnWithImplicitCastFromWhileLoopTest() %void { +fn returnWithImplicitCastFromWhileLoopTest() error!void { while (true) { return; } @@ -116,8 +116,7 @@ test "while with error union condition" { } var numbers_left: i32 = undefined; -error OutOfNumbers; -fn getNumberOrErr() %i32 { +fn getNumberOrErr() error!i32 { return if (numbers_left == 0) error.OutOfNumbers else x: { @@ -205,8 +204,7 @@ fn testContinueOuter() void { fn returnNull() ?i32 { return null; } fn returnMaybe(x: i32) ?i32 { return x; } -error YouWantedAnError; -fn returnError() %i32 { return error.YouWantedAnError; } -fn returnSuccess(x: i32) %i32 { return x; } +fn returnError() error!i32 { return error.YouWantedAnError; } +fn returnSuccess(x: i32) error!i32 { return x; } fn returnFalse() bool { return false; } fn returnTrue() bool { return true; } diff --git a/test/compare_output.zig b/test/compare_output.zig index 6e379c8d1e..2ca2ba262e 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -1,4 +1,6 @@ -const os = @import("std").os; +const builtin = @import("builtin"); +const std = @import("std"); +const os = std.os; const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompareOutputContext) void { @@ -8,14 +10,14 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\ _ = c.puts(c"Hello, world!"); \\ return 0; \\} - , "Hello, world!" ++ os.line_sep); + , "Hello, world!" ++ std.cstr.line_sep); cases.addCase(x: { var tc = cases.create("multiple files with private function", \\use @import("std").io; \\use @import("foo.zig"); \\ - \\pub fn main() %void { + \\pub fn main() void { \\ privateFunction(); \\ const stdout = &(FileOutStream.init(&(getStdOut() catch unreachable)).stream); \\ stdout.print("OK 2\n") catch unreachable; @@ -49,7 +51,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\use @import("foo.zig"); \\use @import("bar.zig"); \\ - \\pub fn main() %void { + \\pub fn main() void { \\ foo_function(); \\ bar_function(); \\} @@ -89,7 +91,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { var tc = cases.create("two files use import each other", \\use @import("a.zig"); \\ - \\pub fn main() %void { + \\pub fn main() void { \\ ok(); \\} , "OK\n"); @@ -118,7 +120,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { cases.add("hello world without libc", \\const io = @import("std").io; \\ - \\pub fn main() %void { + \\pub fn main() void { \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); \\ stdout.print("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a')) catch unreachable; \\} @@ -268,7 +270,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\const z = io.stdin_fileno; \\const x : @typeOf(y) = 1234; \\const y : u16 = 5678; - \\pub fn main() %void { + \\pub fn main() void { \\ var x_local : i32 = print_ok(x); \\} \\fn print_ok(val: @typeOf(x)) @typeOf(foo) { @@ -351,7 +353,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\ fn method(b: &const Bar) bool { return true; } \\}; \\ - \\pub fn main() %void { + \\pub fn main() void { \\ const bar = Bar {.field2 = 13,}; \\ const foo = Foo {.field1 = bar,}; \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); @@ -367,7 +369,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { cases.add("defer with only fallthrough", \\const io = @import("std").io; - \\pub fn main() %void { + \\pub fn main() void { \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); \\ stdout.print("before\n") catch unreachable; \\ defer stdout.print("defer1\n") catch unreachable; @@ -380,7 +382,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { cases.add("defer with return", \\const io = @import("std").io; \\const os = @import("std").os; - \\pub fn main() %void { + \\pub fn main() void { \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); \\ stdout.print("before\n") catch unreachable; \\ defer stdout.print("defer1\n") catch unreachable; @@ -394,10 +396,10 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { cases.add("errdefer and it fails", \\const io = @import("std").io; - \\pub fn main() %void { + \\pub fn main() void { \\ do_test() catch return; \\} - \\fn do_test() %void { + \\fn do_test() !void { \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); \\ stdout.print("before\n") catch unreachable; \\ defer stdout.print("defer1\n") catch unreachable; @@ -406,18 +408,17 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\ defer stdout.print("defer3\n") catch unreachable; \\ stdout.print("after\n") catch unreachable; \\} - \\error IToldYouItWouldFail; - \\fn its_gonna_fail() %void { + \\fn its_gonna_fail() !void { \\ return error.IToldYouItWouldFail; \\} , "before\ndeferErr\ndefer1\n"); cases.add("errdefer and it passes", \\const io = @import("std").io; - \\pub fn main() %void { + \\pub fn main() void { \\ do_test() catch return; \\} - \\fn do_test() %void { + \\fn do_test() !void { \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); \\ stdout.print("before\n") catch unreachable; \\ defer stdout.print("defer1\n") catch unreachable; @@ -426,7 +427,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\ defer stdout.print("defer3\n") catch unreachable; \\ stdout.print("after\n") catch unreachable; \\} - \\fn its_gonna_pass() %void { } + \\fn its_gonna_pass() error!void { } , "before\nafter\ndefer3\ndefer1\n"); cases.addCase(x: { @@ -434,7 +435,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\const foo_txt = @embedFile("foo.txt"); \\const io = @import("std").io; \\ - \\pub fn main() %void { + \\pub fn main() void { \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); \\ stdout.print(foo_txt) catch unreachable; \\} @@ -452,7 +453,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\const os = std.os; \\const allocator = std.debug.global_allocator; \\ - \\pub fn main() %void { + \\pub fn main() !void { \\ var args_it = os.args(); \\ var stdout_file = try io.getStdOut(); \\ var stdout_adapter = io.FileOutStream.init(&stdout_file); @@ -493,7 +494,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\const os = std.os; \\const allocator = std.debug.global_allocator; \\ - \\pub fn main() %void { + \\pub fn main() !void { \\ var args_it = os.args(); \\ var stdout_file = try io.getStdOut(); \\ var stdout_adapter = io.FileOutStream.init(&stdout_file); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 92066d7e0e..f60705aa31 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,220 @@ const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompileErrorContext) void { + cases.add("no else prong on switch on global error set", + \\export fn entry() void { + \\ foo(error.A); + \\} + \\fn foo(a: error) void { + \\ switch (a) { + \\ error.A => {}, + \\ } + \\} + , + ".tmp_source.zig:5:5: error: else prong required when switching on type 'error'"); + + cases.add("inferred error set with no returned error", + \\export fn entry() void { + \\ foo() catch unreachable; + \\} + \\fn foo() !void { + \\} + , + ".tmp_source.zig:4:11: error: function with inferred error set must return at least one possible error"); + + cases.add("error not handled in switch", + \\export fn entry() void { + \\ foo(452) catch |err| switch (err) { + \\ error.Foo => {}, + \\ }; + \\} + \\fn foo(x: i32) !void { + \\ switch (x) { + \\ 0 ... 10 => return error.Foo, + \\ 11 ... 20 => return error.Bar, + \\ 21 ... 30 => return error.Baz, + \\ else => {}, + \\ } + \\} + , + ".tmp_source.zig:2:26: error: error.Baz not handled in switch", + ".tmp_source.zig:2:26: error: error.Bar not handled in switch"); + + cases.add("duplicate error in switch", + \\export fn entry() void { + \\ foo(452) catch |err| switch (err) { + \\ error.Foo => {}, + \\ error.Bar => {}, + \\ error.Foo => {}, + \\ else => {}, + \\ }; + \\} + \\fn foo(x: i32) !void { + \\ switch (x) { + \\ 0 ... 10 => return error.Foo, + \\ 11 ... 20 => return error.Bar, + \\ else => {}, + \\ } + \\} + , + ".tmp_source.zig:5:14: error: duplicate switch value: '@typeOf(foo).ReturnType.ErrorSet.Foo'", + ".tmp_source.zig:3:14: note: other value is here"); + + cases.add("range operator in switch used on error set", + \\export fn entry() void { + \\ try foo(452) catch |err| switch (err) { + \\ error.A ... error.B => {}, + \\ else => {}, + \\ }; + \\} + \\fn foo(x: i32) !void { + \\ switch (x) { + \\ 0 ... 10 => return error.Foo, + \\ 11 ... 20 => return error.Bar, + \\ else => {}, + \\ } + \\} + , + ".tmp_source.zig:3:17: error: operator not allowed for errors"); + + cases.add("inferring error set of function pointer", + \\comptime { + \\ const z: ?fn()!void = null; + \\} + , + ".tmp_source.zig:2:15: error: inferring error set of return type valid only for function definitions"); + + cases.add("access non-existent member of error set", + \\const Foo = error{A}; + \\comptime { + \\ const z = Foo.Bar; + \\} + , + ".tmp_source.zig:3:18: error: no error named 'Bar' in 'Foo'"); + + cases.add("error union operator with non error set LHS", + \\comptime { + \\ const z = i32!i32; + \\} + , + ".tmp_source.zig:2:15: error: expected error set type, found type 'i32'"); + + cases.add("error equality but sets have no common members", + \\const Set1 = error{A, C}; + \\const Set2 = error{B, D}; + \\export fn entry() void { + \\ foo(Set1.A); + \\} + \\fn foo(x: Set1) void { + \\ if (x == Set2.B) { + \\ + \\ } + \\} + , + ".tmp_source.zig:7:11: error: error sets 'Set1' and 'Set2' have no common errors"); + + cases.add("only equality binary operator allowed for error sets", + \\comptime { + \\ const z = error.A > error.B; + \\} + , + ".tmp_source.zig:2:23: error: operator not allowed for errors"); + + cases.add("explicit error set cast known at comptime violates error sets", + \\const Set1 = error {A, B}; + \\const Set2 = error {A, C}; + \\comptime { + \\ var x = Set1.B; + \\ var y = Set2(x); + \\} + , + ".tmp_source.zig:5:17: error: error.B not a member of error set 'Set2'"); + + cases.add("cast error union of global error set to error union of smaller error set", + \\const SmallErrorSet = error{A}; + \\export fn entry() void { + \\ var x: SmallErrorSet!i32 = foo(); + \\} + \\fn foo() error!i32 { + \\ return error.B; + \\} + , + ".tmp_source.zig:3:35: error: expected 'SmallErrorSet!i32', found 'error!i32'", + ".tmp_source.zig:3:35: note: unable to cast global error set into smaller set"); + + cases.add("cast global error set to error set", + \\const SmallErrorSet = error{A}; + \\export fn entry() void { + \\ var x: SmallErrorSet = foo(); + \\} + \\fn foo() error { + \\ return error.B; + \\} + , + ".tmp_source.zig:3:31: error: expected 'SmallErrorSet', found 'error'", + ".tmp_source.zig:3:31: note: unable to cast global error set into smaller set"); + + cases.add("recursive inferred error set", + \\export fn entry() void { + \\ foo() catch unreachable; + \\} + \\fn foo() !void { + \\ try foo(); + \\} + , + ".tmp_source.zig:5:5: error: cannot resolve inferred error set '@typeOf(foo).ReturnType.ErrorSet': function 'foo' not fully analyzed yet"); + + cases.add("implicit cast of error set not a subset", + \\const Set1 = error{A, B}; + \\const Set2 = error{A, C}; + \\export fn entry() void { + \\ foo(Set1.B); + \\} + \\fn foo(set1: Set1) void { + \\ var x: Set2 = set1; + \\} + , + ".tmp_source.zig:7:19: error: expected 'Set2', found 'Set1'", + ".tmp_source.zig:1:23: note: 'error.B' not a member of destination error set"); + + cases.add("int to err global invalid number", + \\const Set1 = error{A, B}; + \\comptime { + \\ var x: usize = 3; + \\ var y = error(x); + \\} + , + ".tmp_source.zig:4:18: error: integer value 3 represents no error"); + + cases.add("int to err non global invalid number", + \\const Set1 = error{A, B}; + \\const Set2 = error{A, C}; + \\comptime { + \\ var x = usize(Set1.B); + \\ var y = Set2(x); + \\} + , + ".tmp_source.zig:5:17: error: integer value 2 represents no error in 'Set2'"); + + cases.add("@memberCount of error", + \\comptime { + \\ _ = @memberCount(error); + \\} + , + ".tmp_source.zig:2:9: error: global error set member count not available at comptime"); + + cases.add("duplicate error value in error set", + \\const Foo = error { + \\ Bar, + \\ Bar, + \\}; + \\export fn entry() void { + \\ const a: Foo = undefined; + \\} + , + ".tmp_source.zig:3:5: error: duplicate error: 'Bar'", + ".tmp_source.zig:2:5: note: other error here"); + cases.add("cast negative integer literal to usize", \\export fn entry() void { \\ const x = usize(-10); @@ -112,12 +326,12 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { cases.add("wrong return type for main", \\pub fn main() f32 { } - , "error: expected return type of main to be 'u8', 'noreturn', 'void', or '%void'"); + , "error: expected return type of main to be 'u8', 'noreturn', 'void', or '!void'"); cases.add("double ?? on main return value", \\pub fn main() ??void { \\} - , "error: expected return type of main to be 'u8', 'noreturn', 'void', or '%void'"); + , "error: expected return type of main to be 'u8', 'noreturn', 'void', or '!void'"); cases.add("bad identifier in function with struct defined inside function which references local const", \\export fn entry() void { @@ -1173,7 +1387,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { \\export fn f() void { \\ try something(); \\} - \\fn something() %void { } + \\fn something() error!void { } , ".tmp_source.zig:2:5: error: expected type 'void', found 'error'"); @@ -1264,7 +1478,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { , ".tmp_source.zig:3:11: error: cannot assign to constant"); cases.add("main function with bogus args type", - \\pub fn main(args: [][]bogus) %void {} + \\pub fn main(args: [][]bogus) !void {} , ".tmp_source.zig:1:23: error: use of undeclared identifier 'bogus'"); cases.add("for loop missing element param", @@ -1396,7 +1610,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { , ".tmp_source.zig:6:13: error: cannot assign to constant"); cases.add("return from defer expression", - \\pub fn testTrickyDefer() %void { + \\pub fn testTrickyDefer() !void { \\ defer canFail() catch {}; \\ \\ defer try canFail(); @@ -1404,7 +1618,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { \\ const a = maybeInt() ?? return; \\} \\ - \\fn canFail() %void { } + \\fn canFail() error!void { } \\ \\pub fn maybeInt() ?i32 { \\ return 0; @@ -1534,7 +1748,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { \\export fn foo() void { \\ bar() catch unreachable; \\} - \\fn bar() %i32 { return 0; } + \\fn bar() error!i32 { return 0; } , ".tmp_source.zig:2:11: error: expression value is ignored"); cases.add("ignored statement value", @@ -1565,7 +1779,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { \\export fn foo() void { \\ defer bar(); \\} - \\fn bar() %i32 { return 0; } + \\fn bar() error!i32 { return 0; } , ".tmp_source.zig:2:14: error: expression value is ignored"); cases.add("dereference an array", @@ -1632,13 +1846,12 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { , ".tmp_source.zig:2:21: error: expected pointer, found 'usize'"); cases.add("too many error values to cast to small integer", - \\error A; error B; error C; error D; error E; error F; error G; error H; - \\const u2 = @IntType(false, 2); - \\fn foo(e: error) u2 { + \\const Error = error { A, B, C, D, E, F, G, H }; + \\fn foo(e: Error) u2 { \\ return u2(e); \\} \\export fn entry() usize { return @sizeOf(@typeOf(foo)); } - , ".tmp_source.zig:4:14: error: too many error values to fit in 'u2'"); + , ".tmp_source.zig:3:14: error: too many error values to fit in 'u2'"); cases.add("asm at compile time", \\comptime { @@ -1821,9 +2034,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { \\export fn foo() void { \\ while (bar()) {} \\} - \\fn bar() %i32 { return 1; } + \\fn bar() error!i32 { return 1; } , - ".tmp_source.zig:2:15: error: expected type 'bool', found '%i32'"); + ".tmp_source.zig:2:15: error: expected type 'bool', found 'error!i32'"); cases.add("while expected nullable, got bool", \\export fn foo() void { @@ -1837,9 +2050,9 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { \\export fn foo() void { \\ while (bar()) |x| {} \\} - \\fn bar() %i32 { return 1; } + \\fn bar() error!i32 { return 1; } , - ".tmp_source.zig:2:15: error: expected nullable type, found '%i32'"); + ".tmp_source.zig:2:15: error: expected nullable type, found 'error!i32'"); cases.add("while expected error union, got bool", \\export fn foo() void { @@ -1983,7 +2196,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { \\fn foo1(args: ...) void {} \\fn foo2(args: ...) void {} \\ - \\pub fn main() %void { + \\pub fn main() !void { \\ foos[0](); \\} , @@ -1995,7 +2208,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { \\fn foo1(arg: var) void {} \\fn foo2(arg: var) void {} \\ - \\pub fn main() %void { + \\pub fn main() !void { \\ foos[0](true); \\} , diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index 3fb2a91544..8b8f612056 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -5,7 +5,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\pub fn main() %void { + \\pub fn main() void { \\ @panic("oh no"); \\} ); @@ -14,7 +14,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\pub fn main() %void { + \\pub fn main() void { \\ const a = []i32{1, 2, 3, 4}; \\ baz(bar(a)); \\} @@ -28,8 +28,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = add(65530, 10); \\ if (x == 0) return error.Whatever; \\} @@ -42,8 +41,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = sub(10, 20); \\ if (x == 0) return error.Whatever; \\} @@ -56,8 +54,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = mul(300, 6000); \\ if (x == 0) return error.Whatever; \\} @@ -70,8 +67,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = neg(-32768); \\ if (x == 32767) return error.Whatever; \\} @@ -84,8 +80,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = div(-32768, -1); \\ if (x == 32767) return error.Whatever; \\} @@ -98,8 +93,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = shl(-16385, 1); \\ if (x == 0) return error.Whatever; \\} @@ -112,8 +106,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = shl(0b0010111111111111, 3); \\ if (x == 0) return error.Whatever; \\} @@ -126,8 +119,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = shr(-16385, 1); \\ if (x == 0) return error.Whatever; \\} @@ -140,8 +132,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = shr(0b0010111111111111, 3); \\ if (x == 0) return error.Whatever; \\} @@ -154,8 +145,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() void { \\ const x = div0(999, 0); \\} \\fn div0(a: i32, b: i32) i32 { @@ -167,8 +157,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = divExact(10, 3); \\ if (x == 0) return error.Whatever; \\} @@ -181,8 +170,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = widenSlice([]u8{1, 2, 3, 4, 5}); \\ if (x.len == 0) return error.Whatever; \\} @@ -195,8 +183,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = shorten_cast(200); \\ if (x == 0) return error.Whatever; \\} @@ -209,8 +196,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() !void { \\ const x = unsigned_cast(-10); \\ if (x == 0) return error.Whatever; \\} @@ -226,20 +212,19 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\ } \\ @import("std").os.exit(0); // test failed \\} - \\error Whatever; - \\pub fn main() %void { + \\pub fn main() void { \\ bar() catch unreachable; \\} - \\fn bar() %void { + \\fn bar() !void { \\ return error.Whatever; \\} ); - cases.addRuntimeSafety("cast integer to error and no code matches", + cases.addRuntimeSafety("cast integer to global error and no code matches", \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\pub fn main() %void { + \\pub fn main() void { \\ _ = bar(9999); \\} \\fn bar(x: u32) error { @@ -247,12 +232,25 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\} ); + cases.addRuntimeSafety("cast integer to non-global error set and no match", + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\const Set1 = error{A, B}; + \\const Set2 = error{A, C}; + \\pub fn main() void { + \\ _ = foo(Set1.B); + \\} + \\fn foo(set1: Set1) Set2 { + \\ return Set2(set1); + \\} + ); + cases.addRuntimeSafety("@alignCast misaligned", \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} - \\error Wrong; - \\pub fn main() %void { + \\pub fn main() !void { \\ var array align(4) = []u32{0x11111111, 0x11111111}; \\ const bytes = ([]u8)(array[0..]); \\ if (foo(bytes) != 0x11111111) return error.Wrong; @@ -274,7 +272,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\ int: u32, \\}; \\ - \\pub fn main() %void { + \\pub fn main() void { \\ var f = Foo { .int = 42 }; \\ bar(&f); \\} diff --git a/test/standalone/brace_expansion/build.zig b/test/standalone/brace_expansion/build.zig index af3160a8c6..7752f599df 100644 --- a/test/standalone/brace_expansion/build.zig +++ b/test/standalone/brace_expansion/build.zig @@ -1,6 +1,6 @@ const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) %void { +pub fn build(b: &Builder) void { const main = b.addTest("main.zig"); main.setBuildMode(b.standardReleaseOptions()); diff --git a/test/standalone/brace_expansion/main.zig b/test/standalone/brace_expansion/main.zig index 995da8bb56..b8eab68a3e 100644 --- a/test/standalone/brace_expansion/main.zig +++ b/test/standalone/brace_expansion/main.zig @@ -6,9 +6,6 @@ const assert = debug.assert; const Buffer = std.Buffer; const ArrayList = std.ArrayList; -error InvalidInput; -error OutOfMem; - const Token = union(enum) { Word: []const u8, OpenBrace, @@ -19,7 +16,7 @@ const Token = union(enum) { var global_allocator: &mem.Allocator = undefined; -fn tokenize(input:[] const u8) %ArrayList(Token) { +fn tokenize(input:[] const u8) !ArrayList(Token) { const State = enum { Start, Word, @@ -71,7 +68,12 @@ const Node = union(enum) { Combine: []Node, }; -fn parse(tokens: &const ArrayList(Token), token_index: &usize) %Node { +const ParseError = error { + InvalidInput, + OutOfMemory, +}; + +fn parse(tokens: &const ArrayList(Token), token_index: &usize) ParseError!Node { const first_token = tokens.items[*token_index]; *token_index += 1; @@ -107,7 +109,7 @@ fn parse(tokens: &const ArrayList(Token), token_index: &usize) %Node { } } -fn expandString(input: []const u8, output: &Buffer) %void { +fn expandString(input: []const u8, output: &Buffer) !void { const tokens = try tokenize(input); if (tokens.len == 1) { return output.resize(0); @@ -135,7 +137,11 @@ fn expandString(input: []const u8, output: &Buffer) %void { } } -fn expandNode(node: &const Node, output: &ArrayList(Buffer)) %void { +const ExpandNodeError = error { + OutOfMemory, +}; + +fn expandNode(node: &const Node, output: &ArrayList(Buffer)) ExpandNodeError!void { assert(output.len == 0); switch (*node) { Node.Scalar => |scalar| { @@ -172,7 +178,7 @@ fn expandNode(node: &const Node, output: &ArrayList(Buffer)) %void { } } -pub fn main() %void { +pub fn main() !void { var stdin_file = try io.getStdIn(); var stdout_file = try io.getStdOut(); diff --git a/test/standalone/issue_339/build.zig b/test/standalone/issue_339/build.zig index 8cf4cfae77..f3ab327006 100644 --- a/test/standalone/issue_339/build.zig +++ b/test/standalone/issue_339/build.zig @@ -1,6 +1,6 @@ const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) %void { +pub fn build(b: &Builder) void { const obj = b.addObject("test", "test.zig"); const test_step = b.step("test", "Test the program"); diff --git a/test/standalone/issue_339/test.zig b/test/standalone/issue_339/test.zig index 1c9480a0a6..f65b9f734e 100644 --- a/test/standalone/issue_339/test.zig +++ b/test/standalone/issue_339/test.zig @@ -1,7 +1,7 @@ const StackTrace = @import("builtin").StackTrace; pub fn panic(msg: []const u8, stack_trace: ?&StackTrace) noreturn { @breakpoint(); while (true) {} } -fn bar() %void {} +fn bar() error!void {} export fn foo() void { bar() catch unreachable; diff --git a/test/standalone/pkg_import/build.zig b/test/standalone/pkg_import/build.zig index 2caea10bfe..bb9416d3c4 100644 --- a/test/standalone/pkg_import/build.zig +++ b/test/standalone/pkg_import/build.zig @@ -1,6 +1,6 @@ const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) %void { +pub fn build(b: &Builder) void { const exe = b.addExecutable("test", "test.zig"); exe.addPackagePath("my_pkg", "pkg.zig"); diff --git a/test/standalone/pkg_import/test.zig b/test/standalone/pkg_import/test.zig index 9575671f2a..ffd2080022 100644 --- a/test/standalone/pkg_import/test.zig +++ b/test/standalone/pkg_import/test.zig @@ -1,6 +1,6 @@ const my_pkg = @import("my_pkg"); const assert = @import("std").debug.assert; -pub fn main() %void { +pub fn main() void { assert(my_pkg.add(10, 20) == 30); } diff --git a/test/standalone/use_alias/build.zig b/test/standalone/use_alias/build.zig index 912058d5a9..ecbba297d8 100644 --- a/test/standalone/use_alias/build.zig +++ b/test/standalone/use_alias/build.zig @@ -1,6 +1,6 @@ const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) %void { +pub fn build(b: &Builder) void { b.addCIncludePath("."); const main = b.addTest("main.zig"); diff --git a/test/tests.zig b/test/tests.zig index 554bb6a2bc..19a4f82b74 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -45,9 +45,6 @@ const test_targets = []TestTarget { }, }; -error TestFailed; -error CompilationIncorrectlySucceeded; - const max_stdout_size = 1 * 1024 * 1024; // 1 MB pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) &build.Step { @@ -248,7 +245,7 @@ pub const CompareOutputContext = struct { return ptr; } - fn make(step: &build.Step) %void { + fn make(step: &build.Step) !void { const self = @fieldParentPtr(RunCompareOutputStep, "step", step); const b = self.context.b; @@ -337,7 +334,7 @@ pub const CompareOutputContext = struct { return ptr; } - fn make(step: &build.Step) %void { + fn make(step: &build.Step) !void { const self = @fieldParentPtr(RuntimeSafetyRunStep, "step", step); const b = self.context.b; @@ -563,7 +560,7 @@ pub const CompileErrorContext = struct { return ptr; } - fn make(step: &build.Step) %void { + fn make(step: &build.Step) !void { const self = @fieldParentPtr(CompileCmpOutputStep, "step", step); const b = self.context.b; @@ -847,7 +844,7 @@ pub const TranslateCContext = struct { return ptr; } - fn make(step: &build.Step) %void { + fn make(step: &build.Step) !void { const self = @fieldParentPtr(TranslateCCmpOutputStep, "step", step); const b = self.context.b; @@ -1045,14 +1042,14 @@ pub const GenHContext = struct { return ptr; } - fn make(step: &build.Step) %void { + fn make(step: &build.Step) !void { const self = @fieldParentPtr(GenHCmpOutputStep, "step", step); const b = self.context.b; warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); const full_h_path = b.pathFromRoot(self.h_path); - const actual_h = try io.readFileAlloc(full_h_path, b.allocator); + const actual_h = try io.readFileAlloc(b.allocator, full_h_path); for (self.case.expected_lines.toSliceConst()) |expected_line| { if (mem.indexOf(u8, actual_h, expected_line) == null) {