commit 0d5ff6f4622a492dddbb1fc2b19b3157237500b1 (tree) parent 68238d5678a4c055bb6f1206254dcac2e0c634f0 Author: Andrew Kelley <superjoe30@gmail.com> Date: Thu, 8 Feb 2018 02:08:45 -0500 error sets - most tests passing Diffstat:
28 files changed, 333 insertions(+), 121 deletions(-)
diff --git a/TODO b/TODO @@ -1,5 +1,6 @@ sed -i 's/\(\bfn .*) \)%\(.*{\)$/\1!\2/g' $(find . -name "*.zig") + the literal translation of `%T` to this new code is `error!T`. however this would not take advantage of error sets. It's recommended to generally have all your functions which return possible @@ -11,6 +12,11 @@ fn foo() !void { then you can return void, or any error, and the error set is inferred. + +you can get the compiler to tell you the possible errors for an inferred error set like this: + +foo() catch |err| switch (err) {}; + // TODO this is an explicit cast and should actually coerce the type erorr set casting @@ -27,3 +33,8 @@ comptime test for err undefined in infer error syntax - ?a!b should be ?(a!b) but it's (?a)!b + +syntax - (error{}!void) as the return type + + +passing a fn()error{}!T to a fn()error!T should be a compile error, they're not compatible diff --git a/doc/docgen.zig b/doc/docgen.zig @@ -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); @@ -596,8 +594,6 @@ 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); @@ -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) { @@ -974,9 +968,6 @@ 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 { const result = try os.ChildProcess.exec(allocator, args, null, null, max_doc_file_size); switch (result.term) { diff --git a/example/cat/main.zig b/example/cat/main.zig @@ -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/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/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 @@ -14,10 +14,6 @@ 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 { @@ -472,7 +468,7 @@ pub fn main2() !void { } } -fn printUsage(stream: &io.OutStream) !void { +fn printUsage(stream: var) !void { try stream.write( \\Usage: zig [command] [options] \\ diff --git a/src-self-hosted/module.zig b/src-self-hosted/module.zig @@ -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(); @@ -265,6 +265,7 @@ pub const Module = struct { 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 { diff --git a/src-self-hosted/parser.zig b/src-self-hosted/parser.zig @@ -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, @@ -555,7 +553,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 +575,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); @@ -694,7 +692,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,7 +700,7 @@ 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); @@ -763,7 +761,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 +800,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); @@ -1058,10 +1056,6 @@ 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 { diff --git a/src/ir.cpp b/src/ir.cpp @@ -5442,6 +5442,10 @@ static TypeTableEntry *get_error_set_union(CodeGen *g, ErrorTableEntry **errors, 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]; @@ -5523,6 +5527,8 @@ static IrInstruction *ir_gen_err_set_decl(IrBuilder *irb, Scope *parent_scope, A err_set_type->data.error_set.errors = allocate<ErrorTableEntry *>(err_count); } + ErrorTableEntry **errors = allocate<ErrorTableEntry *>(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); @@ -5543,7 +5549,16 @@ static IrInstruction *ir_gen_err_set_decl(IrBuilder *irb, Scope *parent_scope, A 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); } @@ -6512,6 +6527,7 @@ static TypeTableEntry *get_error_set_intersection(IrAnalyze *ira, TypeTableEntry ErrorTableEntry **errors = allocate<ErrorTableEntry *>(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<ErrorTableEntry *> intersection_list = {}; @@ -6653,6 +6669,7 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, TypeTableEntry ErrorTableEntry **errors = allocate<ErrorTableEntry *>(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) { @@ -6767,6 +6784,12 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, 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, @@ -6995,6 +7018,12 @@ 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]; @@ -7002,6 +7031,7 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod return ira->codegen->builtin_types.entry_invalid; } 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)) { @@ -7011,9 +7041,11 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod if (!resolve_inferred_error_set(ira, err_set_type, prev_inst->source_node)) { return ira->codegen->builtin_types.entry_invalid; } - errors = allocate<ErrorTableEntry *>(ira->codegen->errors_by_index.length); + 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; } } @@ -7064,6 +7096,9 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod 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; @@ -7084,8 +7119,12 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod 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; @@ -7122,14 +7161,21 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod 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; @@ -7173,15 +7219,18 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod 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; } - errors = allocate<ErrorTableEntry *>(ira->codegen->errors_by_index.length); 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) { @@ -7237,11 +7286,13 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod continue; } + update_errors_helper(ira->codegen, &errors, &errors_count); + if (err_set_type == nullptr) { err_set_type = prev_err_set_type; - errors = allocate<ErrorTableEntry *>(ira->codegen->errors_by_index.length); 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; } } @@ -7262,8 +7313,12 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod 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; @@ -7331,6 +7386,8 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod 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; @@ -8000,6 +8057,7 @@ static IrInstruction *ir_analyze_err_set_cast(IrAnalyze *ira, IrInstruction *sou ErrorTableEntry **errors = allocate<ErrorTableEntry *>(ira->codegen->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; } ErrorMsg *err_msg = nullptr; @@ -10212,8 +10270,9 @@ static TypeTableEntry *ir_analyze_merge_error_sets(IrAnalyze *ira, IrInstruction } ErrorTableEntry **errors = allocate<ErrorTableEntry *>(ira->codegen->errors_by_index.length); - for (uint32_t i = 0; i < op1_type->data.error_set.err_count; i += 1) { + 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); @@ -14987,6 +15046,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; diff --git a/src/util.hpp b/src/util.hpp @@ -92,19 +92,22 @@ static inline void safe_memcpy(T *dest, const T *src, size_t count) { } template<typename T> -static inline T *reallocate_nonzero(T *old, size_t old_count, size_t new_count) { -#ifdef NDEBUG +static inline T *reallocate(T *old, size_t old_count, size_t new_count) { T *ptr = reinterpret_cast<T*>(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; -#else - // manually assign every element to trigger compile error for non-copyable structs - T *ptr = allocate_nonzero<T>(new_count); - safe_memcpy(ptr, old, old_count); - free(old); +} + +template<typename T> +static inline T *reallocate_nonzero(T *old, size_t old_count, size_t new_count) { + T *ptr = reinterpret_cast<T*>(realloc(old, new_count * sizeof(T))); + if (!ptr) + zig_panic("allocation failed"); return ptr; -#endif } template <typename T, size_t n> diff --git a/std/build.zig b/std/build.zig @@ -271,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); @@ -285,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; @@ -1910,7 +1910,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); } @@ -1972,7 +1972,7 @@ pub const Step = struct { self.dependencies.append(other) catch unreachable; } - fn makeNoOp(self: &Step) (error{}!void) {} + fn makeNoOp(self: &Step) error!void {} }; fn doAtomicSymLinks(allocator: &Allocator, output_path: []const u8, filename_major_only: []const u8, diff --git a/std/fmt/index.zig b/std/fmt/index.zig @@ -510,7 +510,7 @@ pub fn allocPrint(allocator: &mem.Allocator, comptime fmt: []const u8, args: ... 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; } diff --git a/std/io.zig b/std/io.zig @@ -694,13 +694,13 @@ pub const BufferOutStream = struct { 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); } diff --git a/std/os/child_process.zig b/std/os/child_process.zig @@ -55,7 +55,22 @@ pub const ChildProcess = struct { 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) { @@ -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); @@ -757,7 +772,7 @@ 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); } diff --git a/std/os/index.zig b/std/os/index.zig @@ -243,7 +243,6 @@ pub const PosixOpenError = error { SystemResources, NoSpaceLeft, NotDir, - AccessDenied, PathAlreadyExists, Unexpected, }; @@ -411,7 +410,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, @@ -904,24 +915,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(); @@ -1252,6 +1307,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()); } @@ -1267,7 +1324,7 @@ pub const ArgIteratorWindows = struct { } /// You must free the returned memory when done. - pub fn next(self: &ArgIteratorWindows, allocator: &Allocator) ?(@typeOf(internalNext).ReturnType.ErrorSet![]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]; @@ -1320,7 +1377,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(); @@ -1394,16 +1451,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 { diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig @@ -30,7 +30,6 @@ pub fn windowsClose(handle: windows.HANDLE) void { pub const WriteError = error { SystemResources, OperationAborted, - SystemResources, IoPending, BrokenPipe, Unexpected, @@ -83,6 +82,8 @@ pub const OpenError = error { AccessDenied, PipeBusy, Unexpected, + OutOfMemory, + NameTooLong, }; /// `file_path` may need to be copied in memory to add a null terminating byte. In this case diff --git 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_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; @@ -43,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; @@ -110,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); @@ -123,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 @@ -181,12 +190,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/test/cases/error.zig b/test/cases/error.zig @@ -1,5 +1,7 @@ -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() error!i32 { const x = try bar(); @@ -74,3 +76,35 @@ fn doErrReturnInAssignment() error!void { 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, + } +} diff --git a/test/compare_output.zig b/test/compare_output.zig @@ -15,7 +15,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { \\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 +49,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 +89,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 +118,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 +268,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 +351,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 +367,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 +380,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,7 +394,7 @@ 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 { @@ -406,7 +406,6 @@ 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 { \\ return error.IToldYouItWouldFail; \\} @@ -414,7 +413,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { 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 { @@ -426,7 +425,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 +433,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; \\} diff --git a/test/compile_errors.zig b/test/compile_errors.zig @@ -1,6 +1,25 @@ const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompileErrorContext) void { + 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("duplicate struct field", \\const Foo = struct { \\ Bar: i32, @@ -99,12 +118,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 { @@ -1160,7 +1179,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'"); @@ -1251,7 +1270,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", @@ -1391,7 +1410,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; @@ -1521,7 +1540,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", @@ -1552,7 +1571,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", @@ -1619,13 +1638,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 { @@ -1808,9 +1826,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 { @@ -1824,9 +1842,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 { diff --git 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 @@ -68,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; @@ -132,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| { diff --git 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 @@ -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 @@ -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 @@ -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 @@ -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");