Commit 47090d234e

Andrew Kelley <andrew@ziglang.org>
2020-06-08 21:15:55
stage2: add passing test for compile error in unreferenced cycle
1 parent 9ea4965
Changed files (3)
src-self-hosted
test
stage2
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;
 }
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();
-
-        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, case.expected_zir, out_zir.items);
+        for (case.updates.items) |update| {
+            var prg_node = update_node.start("", 3);
+            prg_node.activate();
+            defer prg_node.end();
+
+            try tmp.dir.writeFile(tmp_src_path, update.src);
+
+            var module_node = prg_node.start("parse/analysis/codegen", null);
+            module_node.activate();
+            try module.update();
+            module_node.end();
+
+            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);
+                        }
+                    }
+                },
+            }
+        }
     }
 };
 
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)
     {