stage2: add passing test for compile error in unreferenced cycle

master
Andrew Kelley 2020-06-08 15:15:55 -04:00
parent 9ea4965ceb
commit 47090d234e
3 changed files with 230 additions and 29 deletions

View File

@ -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;
}

View File

@ -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);
}
}
},
}
}
}
};

View File

@ -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)
{