diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index c0c52d2ce6..7a61cd5ccd 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -33,6 +33,9 @@ bin_file_path: []const u8, /// Decl pointers to details about them being exported. /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. decl_exports: std.AutoHashMap(*Decl, []*Export), +/// We track which export is associated with the given symbol name for quick +/// detection of symbol collisions. +symbol_exports: std.StringHashMap(*Export), /// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl /// is modified. Note that the key of this table is not the Decl being exported, but the Decl that /// is performing the export of another Decl. @@ -777,6 +780,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .optimize_mode = options.optimize_mode, .decl_table = DeclTable.init(gpa), .decl_exports = std.AutoHashMap(*Decl, []*Export).init(gpa), + .symbol_exports = std.StringHashMap(*Export).init(gpa), .export_owners = std.AutoHashMap(*Decl, []*Export).init(gpa), .failed_decls = std.AutoHashMap(*Decl, *ErrorMsg).init(gpa), .failed_files = std.AutoHashMap(*Scope, *ErrorMsg).init(gpa), @@ -834,6 +838,7 @@ pub fn deinit(self: *Module) void { } self.export_owners.deinit(); } + self.symbol_exports.deinit(); self.root_scope.destroy(allocator); self.* = undefined; } @@ -1732,8 +1737,10 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { for (decls) |src_decl, decl_i| { if (src_decl.cast(ast.Node.FnProto)) |fn_proto| { // We will create a Decl for it regardless of analysis status. - const name_tok = fn_proto.name_token orelse - @panic("TODO handle missing function name in the parser"); + const name_tok = fn_proto.name_token orelse { + @panic("TODO missing function name"); + }; + const name_loc = tree.token_locs[name_tok]; const name = tree.tokenSliceLoc(name_loc); const name_hash = root_scope.fullyQualifiedNameHash(name); @@ -1743,10 +1750,16 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { // Update the AST Node index of the decl, even if its contents are unchanged, it may // have been re-ordered. decl.src_index = decl_i; - deleted_decls.removeAssertDiscard(decl); - if (!srcHashEql(decl.contents_hash, contents_hash)) { - try self.markOutdatedDecl(decl); - decl.contents_hash = contents_hash; + if (deleted_decls.remove(decl) == null) { + decl.analysis = .sema_failure; + const err_msg = try ErrorMsg.create(self.allocator, tree.token_locs[name_tok].start, "redefinition of '{}'", .{decl.name}); + errdefer err_msg.destroy(self.allocator); + try self.failed_decls.putNoClobber(decl, err_msg); + } else { + if (!srcHashEql(decl.contents_hash, contents_hash)) { + try self.markOutdatedDecl(decl); + decl.contents_hash = contents_hash; + } } } else { const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash); @@ -1895,6 +1908,10 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void { } self.bin_file.deleteExport(exp.link); + if (self.failed_exports.remove(exp)) |entry| { + entry.value.destroy(self.allocator); + } + _ = self.symbol_exports.remove(exp.options.name); self.allocator.destroy(exp); } self.allocator.free(kv.value); @@ -2130,6 +2147,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const .Fn => {}, else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}), } + try self.decl_exports.ensureCapacity(self.decl_exports.size + 1); try self.export_owners.ensureCapacity(self.export_owners.size + 1); @@ -2165,6 +2183,20 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const de_gop.kv.value[de_gop.kv.value.len - 1] = new_export; errdefer de_gop.kv.value = self.allocator.shrink(de_gop.kv.value, de_gop.kv.value.len - 1); + if (self.symbol_exports.get(symbol_name)) |_| { + try self.failed_exports.ensureCapacity(self.failed_exports.size + 1); + self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( + self.allocator, + src, + "exported symbol collision: {}", + .{symbol_name}, + )); + // TODO: add a note + new_export.status = .failed; + return; + } + + try self.symbol_exports.putNoClobber(symbol_name, new_export); self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => { diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 14804cab68..38392b8aa7 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -21,9 +21,10 @@ const ErrorMsg = struct { }; pub const TestContext = struct { - zir_cases: std.ArrayList(Case), + /// TODO: find a way to treat cases as individual tests (shouldn't show "1 test passed" if there are 200 cases) + cases: std.ArrayList(Case), - pub const ZIRUpdate = struct { + pub const Update = struct { /// The input to the current update. We simulate an incremental update /// with the file's contents changed to this value each update. /// @@ -33,35 +34,43 @@ pub const TestContext = struct { /// effects of the incremental compilation. src: [:0]const u8, case: union(enum) { - /// A transformation update transforms the input ZIR and tests against + /// A transformation update transforms the input and tests against /// the expected output ZIR. Transformation: [:0]const u8, /// An error update attempts to compile bad code, and ensures that it /// fails to compile, and for the expected reasons. /// A slice containing the expected errors *in sequential order*. Error: []const ErrorMsg, - /// An execution update compiles and runs the input ZIR, feeding in - /// provided input and ensuring that the stdout match what is expected. + /// An execution update compiles and runs the input, testing the + /// stdout against the expected results + /// This is a slice containing the expected message. Execution: []const u8, }, }; - /// A Case consists of a set of *updates*. A update can transform ZIR, - /// compile it, ensure that compilation fails, and more. The same Module is - /// used for each update, so each update's source is treated as a single file - /// being updated by the test harness and incrementally compiled. + pub const TestType = enum { + Zig, + ZIR, + }; + + /// A Case consists of a set of *updates*. The same Module is used for each + /// update, so each update's source is treated as a single file being + /// updated by the test harness and incrementally compiled. pub const Case = struct { + /// The name of the test case. This is shown if a test fails, and + /// otherwise ignored. name: []const u8, - /// The platform the ZIR targets. For non-native platforms, an emulator + /// The platform the test targets. For non-native platforms, an emulator /// such as QEMU is required for tests to complete. target: std.zig.CrossTarget, - updates: std.ArrayList(ZIRUpdate), + /// In order to be able to run e.g. Execution updates, this must be set + /// to Executable. output_mode: std.builtin.OutputMode, - /// Either ".zir" or ".zig" - extension: [4]u8, + updates: std.ArrayList(Update), + extension: TestType, - /// Adds a subcase in which the module is updated with new ZIR, and the - /// resulting ZIR is validated. + /// Adds a subcase in which the module is updated with `src`, and the + /// resulting ZIR is validated against `result`. pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) void { self.updates.append(.{ .src = src, @@ -69,6 +78,8 @@ pub const TestContext = struct { }) catch unreachable; } + /// Adds a subcase in which the module is updated with `src`, compiled, + /// run, and the output is tested against `result`. pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void { self.updates.append(.{ .src = src, @@ -76,31 +87,31 @@ pub const TestContext = struct { }) catch unreachable; } - /// Adds a subcase in which the module is updated with invalid ZIR, and - /// ensures that compilation fails for the expected reasons. - /// - /// Errors must be specified in sequential order. + /// Adds a subcase in which the module is updated with `src`, which + /// should contain invalid input, and ensures that compilation fails + /// for the expected reasons, given in sequential order in `errors` in + /// the form `:line:column: error: message`. pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void { var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; for (errors) |e, i| { if (e[0] != ':') { - std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); + @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); } var cur = e[1..]; var line_index = std.mem.indexOf(u8, cur, ":"); if (line_index == null) { - std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); + @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); } const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number"); cur = cur[line_index.? + 1 ..]; const column_index = std.mem.indexOf(u8, cur, ":"); if (column_index == null) { - std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); + @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); } const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); cur = cur[column_index.? + 2 ..]; if (!std.mem.eql(u8, cur[0..7], "error: ")) { - std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); + @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); } const msg = cur[7..]; @@ -116,123 +127,245 @@ pub const TestContext = struct { } self.updates.append(.{ .src = src, .case = .{ .Error = array } }) catch unreachable; } + + /// Adds a subcase in which the module is updated with `src`, and + /// asserts that it compiles without issue + pub fn compiles(self: *Case, src: [:0]const u8) void { + self.addError(src, &[_][]const u8{}); + } }; - pub fn addExeZIR( - ctx: *TestContext, - name: []const u8, - target: std.zig.CrossTarget, - ) *Case { - const case = Case{ - .name = name, - .target = target, - .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), - .output_mode = .Exe, - .extension = ".zir".*, - }; - ctx.zir_cases.append(case) catch unreachable; - return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; - } - - pub fn addObjZIR( - ctx: *TestContext, - name: []const u8, - target: std.zig.CrossTarget, - ) *Case { - const case = Case{ - .name = name, - .target = target, - .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), - .output_mode = .Obj, - .extension = ".zir".*, - }; - ctx.zir_cases.append(case) catch unreachable; - return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; - } - pub fn addExe( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, + T: TestType, ) *Case { - const case = Case{ + ctx.cases.append(Case{ .name = name, .target = target, - .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), + .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Exe, - .extension = ".zig".*, - }; - ctx.zir_cases.append(case) catch unreachable; - return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; + .extension = T, + }) catch unreachable; + return &ctx.cases.items[ctx.cases.items.len - 1]; + } + + /// Adds a test case for Zig input, producing an executable + pub fn exe(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { + return ctx.addExe(name, target, .Zig); + } + + /// Adds a test case for ZIR input, producing an executable + pub fn exeZIR(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { + return ctx.addExe(name, target, .ZIR); } pub fn addObj( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, + T: TestType, ) *Case { - const case = Case{ + ctx.cases.append(Case{ .name = name, .target = target, - .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), + .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Obj, - .extension = ".zig".*, - }; - ctx.zir_cases.append(case) catch unreachable; - return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; + .extension = T, + }) catch unreachable; + return &ctx.cases.items[ctx.cases.items.len - 1]; } - pub fn addZIRCompareOutput( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_stdout: []const u8, - ) void { - var c = ctx.addExeZIR(name, .{}); - c.addCompareOutput(src, expected_stdout); + /// Adds a test case for Zig input, producing an object file + pub fn obj(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { + return ctx.addObj(name, target, .Zig); + } + + /// Adds a test case for ZIR input, producing an object file + pub fn objZIR(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { + return ctx.addObj(name, target, .ZIR); } pub fn addCompareOutput( ctx: *TestContext, name: []const u8, + T: TestType, src: [:0]const u8, expected_stdout: []const u8, ) void { - var c = ctx.addExe(name, .{}); - c.addCompareOutput(src, expected_stdout); + ctx.addExe(name, .{}, T).addCompareOutput(src, expected_stdout); } - pub fn addZIRTransform( + /// Adds a test case that compiles the Zig source given in `src`, executes + /// it, runs it, and tests the output against `expected_stdout` + pub fn compareOutput( + ctx: *TestContext, + name: []const u8, + src: [:0]const u8, + expected_stdout: []const u8, + ) void { + return ctx.addCompareOutput(name, .Zig, src, expected_stdout); + } + + /// Adds a test case that compiles the ZIR source given in `src`, executes + /// it, runs it, and tests the output against `expected_stdout` + pub fn compareOutputZIR( + ctx: *TestContext, + name: []const u8, + src: [:0]const u8, + expected_stdout: []const u8, + ) void { + ctx.addCompareOutput(name, .ZIR, src, expected_stdout); + } + + pub fn addTransform( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + T: TestType, + src: [:0]const u8, + result: [:0]const u8, + ) void { + ctx.addObj(name, target, T).addTransform(src, result); + } + + /// Adds a test case that compiles the Zig given in `src` to ZIR and tests + /// the ZIR against `result` + pub fn transform( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, src: [:0]const u8, result: [:0]const u8, ) void { - var c = ctx.addObjZIR(name, target); - c.addTransform(src, result); + ctx.addTransform(name, target, .Zig, src, result); } - pub fn addZIRError( + /// Adds a test case that cleans up the ZIR source given in `src`, and + /// tests the resulting ZIR against `result` + pub fn transformZIR( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + result: [:0]const u8, + ) void { + ctx.addTransform(name, target, .ZIR, src, result); + } + + pub fn addError( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + T: TestType, + src: [:0]const u8, + expected_errors: []const []const u8, + ) void { + ctx.addObj(name, target, T).addError(src, expected_errors); + } + + /// Adds a test case that ensures that the Zig given in `src` fails to + /// compile for the expected reasons, given in sequential order in + /// `expected_errors` in the form `:line:column: error: message`. + pub fn compileError( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, src: [:0]const u8, expected_errors: []const []const u8, ) void { - var c = ctx.addObjZIR(name, target); - c.addError(src, expected_errors); + ctx.addError(name, target, .Zig, src, expected_errors); + } + + /// Adds a test case that ensures that the ZIR given in `src` fails to + /// compile for the expected reasons, given in sequential order in + /// `expected_errors` in the form `:line:column: error: message`. + pub fn compileErrorZIR( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, + ) void { + ctx.addError(name, target, .ZIR, src, expected_errors); + } + + pub fn addCompiles( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + T: TestType, + src: [:0]const u8, + ) void { + ctx.addObj(name, target, T).compiles(src); + } + + /// Adds a test case that asserts that the Zig given in `src` compiles + /// without any errors. + pub fn compiles( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + ) void { + ctx.addCompiles(name, target, .Zig, src); + } + + /// Adds a test case that asserts that the ZIR given in `src` compiles + /// without any errors. + pub fn compilesZIR( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + ) void { + ctx.addCompiles(name, target, .ZIR, src); + } + + /// Adds a test case that first ensures that the Zig given in `src` fails + /// to compile for the reasons given in sequential order in + /// `expected_errors` in the form `:line:column: error: message`, then + /// asserts that fixing the source (updating with `fixed_src`) isn't broken + /// by incremental compilation. + pub fn incrementalFailure( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, + fixed_src: [:0]const u8, + ) void { + var case = ctx.addObj(name, target, .Zig); + case.addError(src, expected_errors); + case.compiles(fixed_src); + } + + /// Adds a test case that first ensures that the ZIR given in `src` fails + /// to compile for the reasons given in sequential order in + /// `expected_errors` in the form `:line:column: error: message`, then + /// asserts that fixing the source (updating with `fixed_src`) isn't broken + /// by incremental compilation. + pub fn incrementalFailureZIR( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, + fixed_src: [:0]const u8, + ) void { + var case = ctx.addObj(name, target, .ZIR); + case.addError(src, expected_errors); + case.compiles(fixed_src); } fn init() TestContext { const allocator = std.heap.page_allocator; - return .{ - .zir_cases = std.ArrayList(Case).init(allocator), - }; + return .{ .cases = std.ArrayList(Case).init(allocator) }; } fn deinit(self: *TestContext) void { - for (self.zir_cases.items) |c| { + for (self.cases.items) |c| { for (c.updates.items) |u| { if (u.case == .Error) { c.updates.allocator.free(u.case.Error); @@ -240,26 +373,28 @@ pub const TestContext = struct { } c.updates.deinit(); } - self.zir_cases.deinit(); + self.cases.deinit(); self.* = undefined; } fn run(self: *TestContext) !void { var progress = std.Progress{}; - const root_node = try progress.start("zir", self.zir_cases.items.len); + const root_node = try progress.start("tests", self.cases.items.len); defer root_node.end(); const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{}); - for (self.zir_cases.items) |case| { + for (self.cases.items) |case| { std.testing.base_allocator_instance.reset(); var prg_node = root_node.start(case.name, case.updates.items.len); prg_node.activate(); defer prg_node.end(); - // So that we can see which test case failed when the leak checker goes off. - progress.refresh(); + // So that we can see which test case failed when the leak checker goes off, + // or there's an internal error + progress.initial_delay_ns = 0; + progress.refresh_rate_ns = 0; const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target); try self.runOneCase(std.testing.allocator, &prg_node, case, info.target); @@ -267,17 +402,15 @@ pub const TestContext = struct { } } - fn runOneCase(self: *TestContext, allocator: *Allocator, prg_node: *std.Progress.Node, case: Case, target: std.Target) !void { + fn runOneCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: Case, target: std.Target) !void { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const root_name = "test_case"; - const tmp_src_path = try std.fmt.allocPrint(allocator, "{}{}", .{ root_name, case.extension }); - defer allocator.free(tmp_src_path); + const tmp_src_path = if (case.extension == .Zig) "test_case.zig" else if (case.extension == .ZIR) "test_case.zir" else unreachable; const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); defer root_pkg.destroy(); - const bin_name = try std.zig.binNameAlloc(allocator, root_name, target, case.output_mode, null); + const bin_name = try std.zig.binNameAlloc(allocator, "test_case", target, case.output_mode, null); defer allocator.free(bin_name); var module = try Module.init(allocator, .{ @@ -299,7 +432,7 @@ pub const TestContext = struct { defer module.deinit(); for (case.updates.items) |update, update_index| { - var update_node = prg_node.start("update", 4); + var update_node = root_node.start("update", 3); update_node.activate(); defer update_node.end(); @@ -316,6 +449,7 @@ pub const TestContext = struct { switch (update.case) { .Transformation => |expected_output| { + update_node.estimated_total_items = 5; var emit_node = update_node.start("emit", null); emit_node.activate(); var new_zir_module = try zir.emit(allocator, module); @@ -329,9 +463,26 @@ pub const TestContext = struct { try new_zir_module.writeToStream(allocator, out_zir.outStream()); write_node.end(); - std.testing.expectEqualSlices(u8, expected_output, out_zir.items); + var test_node = update_node.start("assert", null); + test_node.activate(); + defer test_node.end(); + if (expected_output.len != out_zir.items.len) { + std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); + std.process.exit(1); + } + for (expected_output) |e, i| { + if (out_zir.items[i] != e) { + if (expected_output.len != out_zir.items.len) { + std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); + std.process.exit(1); + } + } + } }, .Error => |e| { + var test_node = update_node.start("assert", null); + test_node.activate(); + defer test_node.end(); var handled_errors = try allocator.alloc(bool, e.len); defer allocator.free(handled_errors); for (handled_errors) |*h| { @@ -360,6 +511,7 @@ pub const TestContext = struct { } }, .Execution => |expected_stdout| { + update_node.estimated_total_items = 4; var exec_result = x: { var exec_node = update_node.start("execute", null); exec_node.activate(); @@ -376,6 +528,10 @@ pub const TestContext = struct { .cwd_dir = tmp.dir, }); }; + var test_node = update_node.start("test", null); + test_node.activate(); + defer test_node.end(); + defer allocator.free(exec_result.stdout); defer allocator.free(exec_result.stderr); switch (exec_result.term) { diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 182ff8131a..d49f16876e 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -17,7 +17,7 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = ctx.addExe("hello world with updates", linux_x64); + var case = ctx.exe("hello world with updates", linux_x64); // Regular old hello world case.addCompareOutput( \\export fn _start() noreturn { diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 7596894dca..45e60c0741 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -9,7 +9,7 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - ctx.addZIRError("call undefined local", linux_x64, + ctx.compileErrorZIR("call undefined local", linux_x64, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -19,7 +19,7 @@ pub fn addCases(ctx: *TestContext) !void { // TODO: address inconsistency in this message and the one in the next test , &[_][]const u8{":5:13: error: unrecognized identifier: %test"}); - ctx.addZIRError("call with non-existent target", linux_x64, + ctx.compileErrorZIR("call with non-existent target", linux_x64, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -31,7 +31,7 @@ pub fn addCases(ctx: *TestContext) !void { , &[_][]const u8{":5:13: error: decl 'notafunc' not found"}); // TODO: this error should occur at the call site, not the fntype decl - ctx.addZIRError("call naked function", linux_x64, + ctx.compileErrorZIR("call naked function", linux_x64, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -43,56 +43,91 @@ pub fn addCases(ctx: *TestContext) !void { \\@1 = export(@0, "start") , &[_][]const u8{":4:9: error: unable to call function with naked calling convention"}); + ctx.incrementalFailureZIR("exported symbol collision", linux_x64, + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn) + \\@start = fn(@start_fnty, {}) + \\ + \\@0 = str("_start") + \\@1 = export(@0, "start") + \\@2 = export(@0, "start") + , &[_][]const u8{":8:13: error: exported symbol collision: _start"}, + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn) + \\@start = fn(@start_fnty, {}) + \\ + \\@0 = str("_start") + \\@1 = export(@0, "start") + ); + + ctx.compileError("function redefinition", linux_x64, + \\fn entry() void {} + \\fn entry() void {} + , &[_][]const u8{":2:4: error: redefinition of 'entry'"}); + + //ctx.incrementalFailure("function redefinition", linux_x64, + // \\fn entry() void {} + // \\fn entry() void {} + //, &[_][]const u8{":2:4: error: redefinition of 'entry'"}, + // \\fn entry() void {} + //); + + //// TODO: need to make sure this works with other variants of export. + //ctx.incrementalFailure("exported symbol collision", linux_x64, + // \\export fn entry() void {} + // \\export fn entry() void {} + //, &[_][]const u8{":2:11: error: redefinition of 'entry'"}, + // \\export fn entry() void {} + //); + + // ctx.incrementalFailure("missing function name", linux_x64, + // \\fn() void {} + // , &[_][]const u8{":1:3: error: missing function name"}, + // \\fn a() void {} + // ); + // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 - // TODO: add Zig AST -> ZIR testing pipeline - //try ctx.testCompileError( - // \\export fn entry() void {} - // \\export fn entry() void {} - //, "1.zig", 2, 8, "exported symbol collision: 'entry'"); - - //try ctx.testCompileError( - // \\fn() void {} - //, "1.zig", 1, 1, "missing function name"); - - //try ctx.testCompileError( + //ctx.testCompileError( // \\comptime { // \\ return; // \\} //, "1.zig", 2, 5, "return expression outside function definition"); - //try ctx.testCompileError( + //ctx.testCompileError( // \\export fn entry() void { // \\ defer return; // \\} //, "1.zig", 2, 11, "cannot return from defer expression"); - //try ctx.testCompileError( + //ctx.testCompileError( // \\export fn entry() c_int { // \\ return 36893488147419103232; // \\} //, "1.zig", 2, 12, "integer value '36893488147419103232' cannot be stored in type 'c_int'"); - //try ctx.testCompileError( + //ctx.testCompileError( // \\comptime { // \\ var a: *align(4) align(4) i32 = 0; // \\} //, "1.zig", 2, 22, "Extra align qualifier"); - //try ctx.testCompileError( + //ctx.testCompileError( // \\comptime { // \\ var b: *const const i32 = 0; // \\} //, "1.zig", 2, 19, "Extra align qualifier"); - //try ctx.testCompileError( + //ctx.testCompileError( // \\comptime { // \\ var c: *volatile volatile i32 = 0; // \\} //, "1.zig", 2, 22, "Extra align qualifier"); - //try ctx.testCompileError( + //ctx.testCompileError( // \\comptime { // \\ var d: *allowzero allowzero i32 = 0; // \\} diff --git a/test/stage2/test.zig b/test/stage2/test.zig index dc92f99506..e0ef291588 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -3,5 +3,5 @@ const TestContext = @import("../../src-self-hosted/test.zig").TestContext; pub fn addCases(ctx: *TestContext) !void { try @import("compile_errors.zig").addCases(ctx); try @import("compare_output.zig").addCases(ctx); - @import("zir.zig").addCases(ctx); + try @import("zir.zig").addCases(ctx); } diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index 673be6d99f..052ada667e 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -8,8 +8,8 @@ const linux_x64 = std.zig.CrossTarget{ .os_tag = .linux, }; -pub fn addCases(ctx: *TestContext) void { - ctx.addZIRTransform("referencing decls which appear later in the file", linux_x64, +pub fn addCases(ctx: *TestContext) !void { + ctx.transformZIR("referencing decls which appear later in the file", linux_x64, \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ @@ -32,7 +32,7 @@ pub fn addCases(ctx: *TestContext) void { \\}) \\ ); - ctx.addZIRTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, + ctx.transformZIR("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, \\@void = primitive(void) \\@usize = primitive(usize) \\@fnty = fntype([], @void, cc=C) @@ -86,7 +86,7 @@ pub fn addCases(ctx: *TestContext) void { ); { - var case = ctx.addObjZIR("reference cycle with compile error in the cycle", linux_x64); + var case = ctx.objZIR("reference cycle with compile error in the cycle", linux_x64); case.addTransform( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) @@ -207,7 +207,7 @@ pub fn addCases(ctx: *TestContext) void { return; } - ctx.addZIRCompareOutput("hello world ZIR", + ctx.compareOutputZIR("hello world ZIR", \\@noreturn = primitive(noreturn) \\@void = primitive(void) \\@usize = primitive(usize) @@ -265,7 +265,7 @@ pub fn addCases(ctx: *TestContext) void { \\ ); - ctx.addZIRCompareOutput("function call with no args no return value", + ctx.compareOutputZIR("function call with no args no return value", \\@noreturn = primitive(noreturn) \\@void = primitive(void) \\@usize = primitive(usize)