diff --git a/CHANGELOG.md b/CHANGELOG.md index ca42670084293a5a01d57f840c05c892fb68abdd..1c4670f4bd6384a3956ba51fb73be6d91c17d08f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.1.7 - feat: allow any expression as a function body +- fix: coerce `break` operands and implicit breaks to result type ## 0.1.6 diff --git a/src/core/Sema.zig b/src/core/Sema.zig index 09b1d89f2d0f14ff125e31aada08ad0046f11b6e..1980321f3df13b1830fd8307ca0c3c00cf541af3 100644 --- a/src/core/Sema.zig +++ b/src/core/Sema.zig @@ -312,7 +312,6 @@ const InstMap = struct { pub fn getOrNull(map: *const InstMap, teg_code: Teg.Inst.Index) ?Air.Inst.Ref { const source_i = @intFromEnum(teg_code); const start_i = @intFromEnum(map.start); - assert(source_i >= start_i and source_i <= start_i + map.items.len); if (assertFailed(source_i >= start_i and source_i <= start_i + map.items.len)) { std.debug.panic("attempt fetching instruction %{d} which is out of range of %{d}-%{d}", .{ source_i, @@ -538,11 +537,17 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) const param: Teg.DataNode = s.frame.currentData(.param); assert(s.frame.mode == .run_time); // never emitted in a comptime block assert(s.owner.kind == .func); // only emitted in function body - const param_types = s.owner.unwrap().func.toKey(s.ip).func.ty.toUnresolvedFunctionPrototype(s.ip).?.param_types; - return s.addRef(.arg, .{ - .type = param_types[param.data], - .arg_number = param.data, - }); + const proto = s.owner.unwrap().func.toKey(s.ip).func.ty.toUnresolvedFunctionPrototype(s.ip).?; + switch (param.data) { + std.math.maxInt(u32) => { + const return_type = proto.return_type orelse unreachable; // never emitted for inferred return types + return return_type.value().toAir(); + }, + else => return s.addRef(.arg, .{ + .type = proto.param_types[param.data], + .arg_number = param.data, + }), + } }, .string => { const string: Teg.SlicedString = s.frame.currentPayload(.string); diff --git a/src/core/Teg.Dump.zig b/src/core/Teg.Dump.zig index 71bccf7dbebad6757c530ff9bf2a823f2b64467d..a244b796e151855de91e8d27322795120aec7a99 100644 --- a/src/core/Teg.Dump.zig +++ b/src/core/Teg.Dump.zig @@ -104,8 +104,13 @@ const State = struct { .param => { const data = teg.instData(.param, inst); try writer.writeAll("("); - try tty.setColor(writer, .yellow); - try writer.print("{d}", .{data.data}); + if (data.data == std.math.maxInt(u32)) { + try tty.setColor(writer, .bright_black); + try writer.writeAll(""); + } else { + try tty.setColor(writer, .yellow); + try writer.print("{d}", .{data.data}); + } try tty.setColor(writer, .reset); try writer.writeAll(")"); }, @@ -225,7 +230,7 @@ const State = struct { const call: Teg.Call = teg.instData(.call, inst).extra_index.get(teg); try writer.writeAll("("); try s.dumpRefMarker(writer, call.base); - try writer.writeAll(","); + try writer.writeAll(", "); try s.dumpRefMarkerList(writer, call.args.slice(teg), '[', ']'); try writer.writeAll(")"); }, @@ -233,7 +238,7 @@ const State = struct { const call: Teg.JsNew = teg.instData(.js_new, inst).extra_index.get(teg); try writer.writeAll("("); try s.dumpRefMarker(writer, call.base); - try writer.writeAll(","); + try writer.writeAll(", "); try s.dumpRefMarkerList(writer, call.args.slice(teg), '[', ']'); try writer.writeAll(")"); }, @@ -470,7 +475,7 @@ const State = struct { } fn dumpRefMarkerList(state: *State, writer: anytype, list: []const Teg.Inst.Ref, l: u8, r: u8) !void { - try writer.print(" {c}", .{l}); + try writer.print("{c}", .{l}); if (list.len > 0) { state.indent += 1; diff --git a/src/core/Teg.zig b/src/core/Teg.zig index d13225a790ad79472320df30366cc39e8ad4f597..d207cf72561b3ab027aa7e5859f565dc9b76dd4f 100644 --- a/src/core/Teg.zig +++ b/src/core/Teg.zig @@ -42,6 +42,7 @@ pub const Tag = enum(u8) { /// The resulting Air Ref will be guaranteed to be the coerced type. coercion, /// Load a parameter + /// if data value is maxInt(u32), the function return type is used param, /// Asserts the operand is not void. Used for discard statements. discard, diff --git a/src/core/Transform.zig b/src/core/Transform.zig index cbd8e9b1bae291405216f520e8c9b12e990fbb45..ccead19a922ce962066ccf642886024f01fde15f 100644 --- a/src/core/Transform.zig +++ b/src/core/Transform.zig @@ -631,12 +631,10 @@ const BlockType = enum { compile_time, }; -// todo: clarify /// Visit and convert an expression into either a Value with no instructions, or -/// a single instruction. -/// -/// This is important for function bodies, loop bodies, and branches, so that -/// contents are grouped into a nested block. +/// a block instruction containing the expression. This is important for +/// function bodies, loop bodies, branches, and certain mode-switching contexts +/// such as `comptime` and `@typeOf`. fn transformToBlock( t: *Transform, ctx: BlockContext, @@ -672,6 +670,15 @@ fn transformToBlock( }); param.value = .{ .immutable = ref }; } + + if (result_type == .ref and result_type.ref.decode() == .index) { + result_type_storage = .{ + .ref = try t.addRef(.param, .{ + .src_node = ctx.nodeOffset(node), + .data = std.math.maxInt(u32), + }), + }; + } } const expr = try t.transformExpression( @@ -723,11 +730,27 @@ fn transformToBlock( // The block must end with a break if (expr) |result| { + const result_type_ptr = if (copies_result_type) + &result_type_storage + else + result_type; + const coerced_result = switch (result_type_ptr.*) { + .none, .used => result, + else => try t.addRef(.coercion, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.Coercion, .{ + .type = try result_type_ptr.consume(t) orelse + unreachable, // `.none` already handled + .operand = result, + }), + }), + }; + _ = try t.addIndex(.@"break", .{ .src_node = ctx.nodeOffset(node), .extra_index = try t.extra.append(t.gpa, Teg.Break, .{ .block = ret_block, - .operand = result, + .operand = coerced_result, }), }); } @@ -1404,11 +1427,23 @@ fn transformExpression( else .void; + const coerced_operand = switch (break_result) { + .none, .used => operand, + else => try t.addRef(.coercion, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.Coercion, .{ + .type = try break_result.consume(t) orelse + unreachable, // `.none` already handled + .operand = operand, + }), + }), + }; + _ = try t.addIndex(.@"break", .{ .src_node = ctx.nodeOffset(node), .extra_index = try t.extra.append(t.gpa, Teg.Break, .{ .block = try block.getInst(t), - .operand = operand, + .operand = coerced_operand, }), }); diff --git a/test/behavior/function b/test/behavior/function index 249aa23b8a9847afad505ae274d34bbab52be37a..0ddad178342116254da0c255ccc29db2e2be0f1e 100644 --- a/test/behavior/function +++ b/test/behavior/function @@ -6,8 +6,6 @@ fn hello(n: int) -> int { if n > 1 { hello(n - 1) + 1 } else { - // TODO: fix a missing coercion - let i: int = 1; i } } diff --git a/test/behavior/tuple b/test/behavior/tuple index 1884912b9b8b076ac3db1d0fdb7f81380f3b88dc..e21aa02523f77bba5f772493967f2bace284e1c2 100644 --- a/test/behavior/tuple +++ b/test/behavior/tuple @@ -10,5 +10,5 @@ fn main { assertEqual(x.1, f(2.5)); } -fn f(a: float) -> float { a } -fn i(a: int) -> int { a } +fn f(a: float) { a } +fn i(a: int) { a } diff --git a/test/compiler_tests.zig b/test/compiler_tests.zig index f643ca113822df469b333379aaf3f7d9201b523d..c38819efb797a6a9ed5d841aea241aaadc634da3 100644 --- a/test/compiler_tests.zig +++ b/test/compiler_tests.zig @@ -211,6 +211,10 @@ pub fn runTestCaseWrap( defer case.logs = gpa.dupe(u8, logs.items) catch "[logs failed to allocate]"; + std.debug.assert(current_case == null); + current_case = case.*; + defer current_case = null; + runTestCase( alloc, case.path, @@ -497,6 +501,14 @@ fn getFlag(name: []const u8, full_arg: []const u8) ?[]const u8 { return null; } +threadlocal var current_case: ?Case = null; +pub fn panic(msg: []const u8, trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize) noreturn { + if (current_case) |case| { + std.debug.print("Crash during case: {s}\n", .{case.display}); + } + std.debug.panicImpl(trace, first_trace_addr, msg); +} + const std = @import("std"); const Dir = std.fs.Dir;