diff options
| author | Andrew Kelley <superjoe30@gmail.com> | 2018-07-11 00:50:17 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-07-11 00:50:17 -0400 |
| commit | c620a1fe3d40d25d6b6654a4c828e8d7d7e37c38 (patch) | |
| tree | 4cab564fe918fcc167bfcd52c3a619607f0fc364 /src-self-hosted | |
| parent | 8fba0a6ae862993afa2aeca774347adc399b3605 (diff) | |
| parent | 8197a14ceb2938c64526c7b84e2ac8da343960fa (diff) | |
| download | zig-c620a1fe3d40d25d6b6654a4c828e8d7d7e37c38.tar.gz zig-c620a1fe3d40d25d6b6654a4c828e8d7d7e37c38.zip | |
Merge pull request #1215 from ziglang/self-hosted-first-test
self-hosted: first passing test
Diffstat (limited to 'src-self-hosted')
| -rw-r--r-- | src-self-hosted/errmsg.zig | 18 | ||||
| -rw-r--r-- | src-self-hosted/introspect.zig | 5 | ||||
| -rw-r--r-- | src-self-hosted/main.zig | 36 | ||||
| -rw-r--r-- | src-self-hosted/module.zig | 98 | ||||
| -rw-r--r-- | src-self-hosted/test.zig | 164 |
5 files changed, 280 insertions, 41 deletions
diff --git a/src-self-hosted/errmsg.zig b/src-self-hosted/errmsg.zig index b6fd78d8f6..a92b5145ce 100644 --- a/src-self-hosted/errmsg.zig +++ b/src-self-hosted/errmsg.zig @@ -11,11 +11,15 @@ pub const Color = enum { On, }; +pub const Span = struct { + first: ast.TokenIndex, + last: ast.TokenIndex, +}; + pub const Msg = struct { path: []const u8, text: []u8, - first_token: TokenIndex, - last_token: TokenIndex, + span: Span, tree: *ast.Tree, }; @@ -39,8 +43,10 @@ pub fn createFromParseError( .tree = tree, .path = path, .text = text_buf.toOwnedSlice(), - .first_token = loc_token, - .last_token = loc_token, + .span = Span{ + .first = loc_token, + .last = loc_token, + }, }); errdefer allocator.destroy(msg); @@ -48,8 +54,8 @@ pub fn createFromParseError( } pub fn printToStream(stream: var, msg: *const Msg, color_on: bool) !void { - const first_token = msg.tree.tokens.at(msg.first_token); - const last_token = msg.tree.tokens.at(msg.last_token); + const first_token = msg.tree.tokens.at(msg.span.first); + const last_token = msg.tree.tokens.at(msg.span.last); const start_loc = msg.tree.tokenLocationPtr(0, first_token); const end_loc = msg.tree.tokenLocationPtr(first_token.end, last_token); if (!color_on) { diff --git a/src-self-hosted/introspect.zig b/src-self-hosted/introspect.zig index 74084b48c6..ecd04c4467 100644 --- a/src-self-hosted/introspect.zig +++ b/src-self-hosted/introspect.zig @@ -53,3 +53,8 @@ pub fn resolveZigLibDir(allocator: *mem.Allocator) ![]u8 { return error.ZigLibDirNotFound; }; } + +/// Caller must free result +pub fn resolveZigCacheDir(allocator: *mem.Allocator) ![]u8 { + return std.mem.dupe(allocator, u8, "zig-cache"); +} diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index fe94a4460a..d7ead0ba32 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -481,29 +481,29 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo module.link_out_file = flags.single("out-file"); try module.build(); - const process_build_events_handle = try async<loop.allocator> processBuildEvents(module, true); + const process_build_events_handle = try async<loop.allocator> processBuildEvents(module, color); defer cancel process_build_events_handle; loop.run(); } -async fn processBuildEvents(module: *Module, watch: bool) void { - while (watch) { - // TODO directly awaiting async should guarantee memory allocation elision - const build_event = await (async module.events.get() catch unreachable); +async fn processBuildEvents(module: *Module, color: errmsg.Color) void { + // TODO directly awaiting async should guarantee memory allocation elision + const build_event = await (async module.events.get() catch unreachable); - switch (build_event) { - Module.Event.Ok => { - std.debug.warn("Build succeeded\n"); - return; - }, - Module.Event.Error => |err| { - std.debug.warn("build failed: {}\n", @errorName(err)); - @panic("TODO error return trace"); - }, - Module.Event.Fail => |errs| { - @panic("TODO print compile error messages"); - }, - } + switch (build_event) { + Module.Event.Ok => { + std.debug.warn("Build succeeded\n"); + return; + }, + Module.Event.Error => |err| { + std.debug.warn("build failed: {}\n", @errorName(err)); + @panic("TODO error return trace"); + }, + Module.Event.Fail => |msgs| { + for (msgs) |msg| { + errmsg.printToFile(&stderr_file, msg, color) catch os.exit(1); + } + }, } } diff --git a/src-self-hosted/module.zig b/src-self-hosted/module.zig index 24be228eb8..44954e4cd1 100644 --- a/src-self-hosted/module.zig +++ b/src-self-hosted/module.zig @@ -89,12 +89,9 @@ pub const Module = struct { /// the build is complete. build_group: event.Group(BuildError!void), - const BuildErrorsList = std.SegmentedList(BuildErrorDesc, 1); + compile_errors: event.Locked(CompileErrList), - pub const BuildErrorDesc = struct { - code: BuildError, - text: []const u8, - }; + const CompileErrList = std.ArrayList(*errmsg.Msg); // TODO handle some of these earlier and report them in a way other than error codes pub const BuildError = error{ @@ -131,11 +128,12 @@ pub const Module = struct { NoStdHandles, Overflow, NotSupported, + BufferTooSmall, }; pub const Event = union(enum) { Ok, - Fail: []errmsg.Msg, + Fail: []*errmsg.Msg, Error: BuildError, }; @@ -249,6 +247,7 @@ pub const Module = struct { .link_out_file = null, .exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)), .build_group = event.Group(BuildError!void).init(loop), + .compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)), }); } @@ -288,7 +287,17 @@ pub const Module = struct { await (async self.events.put(Event{ .Error = err }) catch unreachable); return; }; - await (async self.events.put(Event.Ok) catch unreachable); + const compile_errors = blk: { + const held = await (async self.compile_errors.acquire() catch unreachable); + defer held.release(); + break :blk held.value.toOwnedSlice(); + }; + + if (compile_errors.len == 0) { + await (async self.events.put(Event.Ok) catch unreachable); + } else { + await (async self.events.put(Event{ .Fail = compile_errors }) catch unreachable); + } // for now we stop after 1 return; } @@ -310,10 +319,13 @@ pub const Module = struct { }; errdefer self.a().free(source_code); - var parsed_file = ParsedFile{ - .tree = try std.zig.parse(self.a(), source_code), + const parsed_file = try self.a().create(ParsedFile{ + .tree = undefined, .realpath = root_src_real_path, - }; + }); + errdefer self.a().destroy(parsed_file); + + parsed_file.tree = try std.zig.parse(self.a(), source_code); errdefer parsed_file.tree.deinit(); const tree = &parsed_file.tree; @@ -337,7 +349,7 @@ pub const Module = struct { const name = if (fn_proto.name_token) |name_token| tree.tokenSlice(name_token) else { @panic("TODO add compile error"); //try self.addCompileError( - // &parsed_file, + // parsed_file, // fn_proto.fn_token, // fn_proto.fn_token + 1, // "missing function name", @@ -357,7 +369,7 @@ pub const Module = struct { }); errdefer self.a().destroy(fn_decl); - try decl_group.call(addTopLevelDecl, self, tree, &fn_decl.base); + try decl_group.call(addTopLevelDecl, self, parsed_file, &fn_decl.base); }, ast.Node.Id.TestDecl => @panic("TODO"), else => unreachable, @@ -367,20 +379,56 @@ pub const Module = struct { try await (async self.build_group.wait() catch unreachable); } - async fn addTopLevelDecl(self: *Module, tree: *ast.Tree, decl: *Decl) !void { - const is_export = decl.isExported(tree); + async fn addTopLevelDecl(self: *Module, parsed_file: *ParsedFile, decl: *Decl) !void { + const is_export = decl.isExported(&parsed_file.tree); if (is_export) { - try self.build_group.call(verifyUniqueSymbol, self, decl); + try self.build_group.call(verifyUniqueSymbol, self, parsed_file, decl); } } - async fn verifyUniqueSymbol(self: *Module, decl: *Decl) !void { + fn addCompileError(self: *Module, parsed_file: *ParsedFile, span: errmsg.Span, comptime fmt: []const u8, args: ...) !void { + const text = try std.fmt.allocPrint(self.loop.allocator, fmt, args); + errdefer self.loop.allocator.free(text); + + try self.build_group.call(addCompileErrorAsync, self, parsed_file, span.first, span.last, text); + } + + async fn addCompileErrorAsync( + self: *Module, + parsed_file: *ParsedFile, + first_token: ast.TokenIndex, + last_token: ast.TokenIndex, + text: []u8, + ) !void { + const msg = try self.loop.allocator.create(errmsg.Msg{ + .path = parsed_file.realpath, + .text = text, + .span = errmsg.Span{ + .first = first_token, + .last = last_token, + }, + .tree = &parsed_file.tree, + }); + errdefer self.loop.allocator.destroy(msg); + + const compile_errors = await (async self.compile_errors.acquire() catch unreachable); + defer compile_errors.release(); + + try compile_errors.value.append(msg); + } + + async fn verifyUniqueSymbol(self: *Module, parsed_file: *ParsedFile, decl: *Decl) !void { const exported_symbol_names = await (async self.exported_symbol_names.acquire() catch unreachable); defer exported_symbol_names.release(); if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| { - @panic("TODO report compile error"); + try self.addCompileError( + parsed_file, + decl.getSpan(), + "exported symbol collision: '{}'", + decl.name, + ); } } @@ -503,6 +551,22 @@ pub const Decl = struct { } } + pub fn getSpan(base: *const Decl) errmsg.Span { + switch (base.id) { + Id.Fn => { + const fn_decl = @fieldParentPtr(Fn, "base", base); + const fn_proto = fn_decl.fn_proto; + const start = fn_proto.fn_token; + const end = fn_proto.name_token orelse start; + return errmsg.Span{ + .first = start, + .last = end + 1, + }; + }, + else => @panic("TODO"), + } + } + pub const Resolution = enum { Unresolved, InProgress, diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig new file mode 100644 index 0000000000..01a857f21d --- /dev/null +++ b/src-self-hosted/test.zig @@ -0,0 +1,164 @@ +const std = @import("std"); +const mem = std.mem; +const builtin = @import("builtin"); +const Target = @import("target.zig").Target; +const Module = @import("module.zig").Module; +const introspect = @import("introspect.zig"); +const assertOrPanic = std.debug.assertOrPanic; +const errmsg = @import("errmsg.zig"); + +test "compile errors" { + var ctx: TestContext = undefined; + try ctx.init(); + defer ctx.deinit(); + + try ctx.testCompileError( + \\export fn entry() void {} + \\export fn entry() void {} + , file1, 2, 8, "exported symbol collision: 'entry'"); + + try ctx.run(); +} + +const file1 = "1.zig"; +const allocator = std.heap.c_allocator; + +const TestContext = struct { + loop: std.event.Loop, + zig_lib_dir: []u8, + zig_cache_dir: []u8, + file_index: std.atomic.Int(usize), + group: std.event.Group(error!void), + any_err: error!void, + + const tmp_dir_name = "stage2_test_tmp"; + + fn init(self: *TestContext) !void { + self.* = TestContext{ + .any_err = {}, + .loop = undefined, + .zig_lib_dir = undefined, + .zig_cache_dir = undefined, + .group = undefined, + .file_index = std.atomic.Int(usize).init(0), + }; + + try self.loop.initMultiThreaded(allocator); + errdefer self.loop.deinit(); + + self.group = std.event.Group(error!void).init(&self.loop); + errdefer self.group.cancelAll(); + + self.zig_lib_dir = try introspect.resolveZigLibDir(allocator); + errdefer allocator.free(self.zig_lib_dir); + + self.zig_cache_dir = try introspect.resolveZigCacheDir(allocator); + errdefer allocator.free(self.zig_cache_dir); + + try std.os.makePath(allocator, tmp_dir_name); + errdefer std.os.deleteTree(allocator, tmp_dir_name) catch {}; + } + + fn deinit(self: *TestContext) void { + std.os.deleteTree(allocator, tmp_dir_name) catch {}; + allocator.free(self.zig_cache_dir); + allocator.free(self.zig_lib_dir); + self.loop.deinit(); + } + + fn run(self: *TestContext) !void { + const handle = try self.loop.call(waitForGroup, self); + defer cancel handle; + self.loop.run(); + return self.any_err; + } + + async fn waitForGroup(self: *TestContext) void { + self.any_err = await (async self.group.wait() catch unreachable); + } + + fn testCompileError( + self: *TestContext, + source: []const u8, + path: []const u8, + line: usize, + column: usize, + msg: []const u8, + ) !void { + var file_index_buf: [20]u8 = undefined; + const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", self.file_index.next()); + const file1_path = try std.os.path.join(allocator, tmp_dir_name, file_index, file1); + + if (std.os.path.dirname(file1_path)) |dirname| { + try std.os.makePath(allocator, dirname); + } + + // TODO async I/O + try std.io.writeFile(allocator, file1_path, source); + + var module = try Module.create( + &self.loop, + "test", + file1_path, + Target.Native, + Module.Kind.Obj, + builtin.Mode.Debug, + self.zig_lib_dir, + self.zig_cache_dir, + ); + errdefer module.destroy(); + + try module.build(); + + try self.group.call(getModuleEvent, module, source, path, line, column, msg); + } + + async fn getModuleEvent( + module: *Module, + source: []const u8, + path: []const u8, + line: usize, + column: usize, + text: []const u8, + ) !void { + defer module.destroy(); + const build_event = await (async module.events.get() catch unreachable); + + switch (build_event) { + Module.Event.Ok => { + @panic("build incorrectly succeeded"); + }, + Module.Event.Error => |err| { + @panic("build incorrectly failed"); + }, + Module.Event.Fail => |msgs| { + assertOrPanic(msgs.len != 0); + for (msgs) |msg| { + if (mem.endsWith(u8, msg.path, path) and mem.eql(u8, msg.text, text)) { + const first_token = msg.tree.tokens.at(msg.span.first); + const last_token = msg.tree.tokens.at(msg.span.first); + const start_loc = msg.tree.tokenLocationPtr(0, first_token); + if (start_loc.line + 1 == line and start_loc.column + 1 == column) { + return; + } + } + } + std.debug.warn( + "\n=====source:=======\n{}\n====expected:========\n{}:{}:{}: error: {}\n", + source, + path, + line, + column, + text, + ); + std.debug.warn("\n====found:========\n"); + var stderr = try std.io.getStdErr(); + for (msgs) |msg| { + try errmsg.printToFile(&stderr, msg, errmsg.Color.Auto); + } + std.debug.warn("============\n"); + return error.TestFailed; + }, + } + } +}; |
