From 47090d234ecc3e50937c918b05e6f039a53d880c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Jun 2020 15:15:55 -0400 Subject: [PATCH] stage2: add passing test for compile error in unreferenced cycle --- src-self-hosted/Module.zig | 6 +- src-self-hosted/test.zig | 134 ++++++++++++++++++++++++++++++------- test/stage2/zir.zig | 119 ++++++++++++++++++++++++++++++++ 3 files changed, 230 insertions(+), 29 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 570ae69a63..4bcc30a65e 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -673,8 +673,8 @@ pub fn getAllErrorsAlloc(self: *Module) !AllErrors { assert(errors.items.len == self.totalErrorCount()); return AllErrors{ - .arena = arena.state, .list = try arena.allocator.dupe(AllErrors.Message, errors.items), + .arena = arena.state, }; } @@ -935,7 +935,7 @@ fn deleteDecl(self: *Module, decl: *Decl) !void { } } if (self.failed_decls.remove(decl)) |entry| { - self.allocator.destroy(entry.value); + entry.value.destroy(self.allocator); } self.deleteDeclExports(decl); self.bin_file.freeDecl(decl); @@ -1104,7 +1104,7 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void { //std.debug.warn("mark {} outdated\n", .{decl.name}); try self.work_queue.writeItem(.{ .re_analyze_decl = decl }); if (self.failed_decls.remove(decl)) |entry| { - self.allocator.destroy(entry.value); + entry.value.destroy(self.allocator); } decl.analysis = .outdated; } diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 451bba996a..605c973bb9 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -27,9 +27,32 @@ pub const TestContext = struct { pub const ZIRTransformCase = struct { name: []const u8, - src: [:0]const u8, - expected_zir: []const u8, cross_target: std.zig.CrossTarget, + updates: std.ArrayList(Update), + + pub const Update = struct { + expected: Expected, + src: [:0]const u8, + }; + + pub const Expected = union(enum) { + zir: []const u8, + errors: []const []const u8, + }; + + pub fn addZIR(case: *ZIRTransformCase, src: [:0]const u8, zir_text: []const u8) void { + case.updates.append(.{ + .src = src, + .expected = .{ .zir = zir_text }, + }) catch unreachable; + } + + pub fn addError(case: *ZIRTransformCase, src: [:0]const u8, errors: []const []const u8) void { + case.updates.append(.{ + .src = src, + .expected = .{ .errors = errors }, + }) catch unreachable; + } }; pub fn addZIRCompareOutput( @@ -52,14 +75,32 @@ pub const TestContext = struct { src: [:0]const u8, expected_zir: []const u8, ) void { - ctx.zir_transform_cases.append(.{ + const case = ctx.zir_transform_cases.addOne() catch unreachable; + case.* = .{ .name = name, - .src = src, - .expected_zir = expected_zir, .cross_target = cross_target, + .updates = std.ArrayList(ZIRTransformCase.Update).init(std.heap.page_allocator), + }; + case.updates.append(.{ + .src = src, + .expected = .{ .zir = expected_zir }, }) catch unreachable; } + pub fn addZIRMulti( + ctx: *TestContext, + name: []const u8, + cross_target: std.zig.CrossTarget, + ) *ZIRTransformCase { + const case = ctx.zir_transform_cases.addOne() catch unreachable; + case.* = .{ + .name = name, + .cross_target = cross_target, + .updates = std.ArrayList(ZIRTransformCase.Update).init(std.heap.page_allocator), + }; + return case; + } + fn init(self: *TestContext) !void { self.* = .{ .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(std.heap.page_allocator), @@ -178,13 +219,11 @@ pub const TestContext = struct { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - var prg_node = root_node.start(case.name, 3); - prg_node.activate(); - defer prg_node.end(); + var update_node = root_node.start(case.name, case.updates.items.len); + update_node.activate(); + defer update_node.end(); const tmp_src_path = "test-case.zir"; - try tmp.dir.writeFile(tmp_src_path, case.src); - const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); defer root_pkg.destroy(); @@ -198,25 +237,68 @@ pub const TestContext = struct { }); defer module.deinit(); - var module_node = prg_node.start("parse/analysis/codegen", null); - module_node.activate(); - try module.update(); - module_node.end(); + for (case.updates.items) |update| { + var prg_node = update_node.start("", 3); + prg_node.activate(); + defer prg_node.end(); - var emit_node = prg_node.start("emit", null); - emit_node.activate(); - var new_zir_module = try zir.emit(allocator, module); - defer new_zir_module.deinit(allocator); - emit_node.end(); + try tmp.dir.writeFile(tmp_src_path, update.src); - var write_node = prg_node.start("write", null); - write_node.activate(); - var out_zir = std.ArrayList(u8).init(allocator); - defer out_zir.deinit(); - try new_zir_module.writeToStream(allocator, out_zir.outStream()); - write_node.end(); + var module_node = prg_node.start("parse/analysis/codegen", null); + module_node.activate(); + try module.update(); + module_node.end(); - std.testing.expectEqualSlices(u8, case.expected_zir, out_zir.items); + switch (update.expected) { + .zir => |expected_zir| { + var emit_node = prg_node.start("emit", null); + emit_node.activate(); + var new_zir_module = try zir.emit(allocator, module); + defer new_zir_module.deinit(allocator); + emit_node.end(); + + var write_node = prg_node.start("write", null); + write_node.activate(); + var out_zir = std.ArrayList(u8).init(allocator); + defer out_zir.deinit(); + try new_zir_module.writeToStream(allocator, out_zir.outStream()); + write_node.end(); + + std.testing.expectEqualSlices(u8, expected_zir, out_zir.items); + }, + .errors => |expected_errors| { + var all_errors = try module.getAllErrorsAlloc(); + defer all_errors.deinit(module.allocator); + for (expected_errors) |expected_error| { + for (all_errors.list) |full_err_msg| { + const text = try std.fmt.allocPrint(allocator, ":{}:{}: error: {}", .{ + full_err_msg.line + 1, + full_err_msg.column + 1, + full_err_msg.msg, + }); + defer allocator.free(text); + if (std.mem.eql(u8, text, expected_error)) { + break; + } + } else { + std.debug.warn( + "{}\nExpected this error:\n================\n{}\n================\nBut found these errors:\n================\n", + .{ case.name, expected_error }, + ); + for (all_errors.list) |full_err_msg| { + std.debug.warn(":{}:{}: error: {}\n", .{ + full_err_msg.line + 1, + full_err_msg.column + 1, + full_err_msg.msg, + }); + } + std.debug.warn("================\nTest failed\n", .{}); + std.process.exit(1); + } + } + }, + } + } } }; diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index 7d5e330b89..bf5d4b8eae 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -90,6 +90,125 @@ pub fn addCases(ctx: *TestContext) void { \\ ); + { + var case = ctx.addZIRMulti("reference cycle with compile error in the cycle", linux_x64); + case.addZIR( + \\@void = primitive(void) + \\@fnty = fntype([], @void, cc=C) + \\ + \\@9 = str("entry") + \\@10 = ref(@9) + \\@11 = export(@10, @entry) + \\ + \\@entry = fn(@fnty, { + \\ %0 = call(@a, []) + \\ %1 = return() + \\}) + \\ + \\@a = fn(@fnty, { + \\ %0 = call(@b, []) + \\ %1 = return() + \\}) + \\ + \\@b = fn(@fnty, { + \\ %0 = call(@a, []) + \\ %1 = return() + \\}) + , + \\@void = primitive(void) + \\@fnty = fntype([], @void, cc=C) + \\@9 = str("entry") + \\@10 = ref(@9) + \\@unnamed$6 = str("entry") + \\@unnamed$7 = ref(@unnamed$6) + \\@unnamed$8 = export(@unnamed$7, @entry) + \\@unnamed$12 = fntype([], @void, cc=C) + \\@entry = fn(@unnamed$12, { + \\ %0 = call(@a, [], modifier=auto) + \\ %1 = return() + \\}) + \\@unnamed$17 = fntype([], @void, cc=C) + \\@a = fn(@unnamed$17, { + \\ %0 = call(@b, [], modifier=auto) + \\ %1 = return() + \\}) + \\@unnamed$22 = fntype([], @void, cc=C) + \\@b = fn(@unnamed$22, { + \\ %0 = call(@a, [], modifier=auto) + \\ %1 = return() + \\}) + \\ + ); + // Now we introduce a compile error + case.addError( + \\@void = primitive(void) + \\@fnty = fntype([], @void, cc=C) + \\ + \\@9 = str("entry") + \\@10 = ref(@9) + \\@11 = export(@10, @entry) + \\ + \\@entry = fn(@fnty, { + \\ %0 = call(@a, []) + \\ %1 = return() + \\}) + \\ + \\@a = fn(@fnty, { + \\ %0 = call(@b, []) + \\ %1 = return() + \\}) + \\ + \\@b = fn(@fnty, { + \\ %9 = compileerror("message") + \\ %0 = call(@a, []) + \\ %1 = return() + \\}) + , + &[_][]const u8{ + ":19:21: error: message", + }, + ); + // Now we remove the call to `a`. `a` and `b` form a cycle, but no entry points are + // referencing either of them. This tests that the cycle is detected, and the error + // goes away. + case.addZIR( + \\@void = primitive(void) + \\@fnty = fntype([], @void, cc=C) + \\ + \\@9 = str("entry") + \\@10 = ref(@9) + \\@11 = export(@10, @entry) + \\ + \\@entry = fn(@fnty, { + \\ %1 = return() + \\}) + \\ + \\@a = fn(@fnty, { + \\ %0 = call(@b, []) + \\ %1 = return() + \\}) + \\ + \\@b = fn(@fnty, { + \\ %9 = compileerror("message") + \\ %0 = call(@a, []) + \\ %1 = return() + \\}) + , + \\@void = primitive(void) + \\@fnty = fntype([], @void, cc=C) + \\@9 = str("entry") + \\@10 = ref(@9) + \\@unnamed$6 = str("entry") + \\@unnamed$7 = ref(@unnamed$6) + \\@unnamed$8 = export(@unnamed$7, @entry) + \\@unnamed$10 = fntype([], @void, cc=C) + \\@entry = fn(@unnamed$10, { + \\ %0 = return() + \\}) + \\ + ); + } + if (std.Target.current.os.tag != .linux or std.Target.current.cpu.arch != .x86_64) {