From 23604fa655b6a34c07acc49cf9b91f93193f705b Mon Sep 17 00:00:00 2001 From: dave caruso Date: Fri, 20 Sep 2024 00:13:45 -0700 Subject: [PATCH] fix: coerce results when `if` expression branches merge --- CHANGELOG.md | 1 + src/core/Comp.zig | 3 +- src/core/Sema.zig | 206 +++++++++++++++++++++++------------------ test/behavior/function | 2 +- 4 files changed, 118 insertions(+), 94 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c4670f4bd6384a3956ba51fb73be6d91c17d08f..dd71745e131b8617bc823abc843657391a016c7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - feat: allow any expression as a function body - fix: coerce `break` operands and implicit breaks to result type +- fix: coerce results when `if` expression branches merge ## 0.1.6 diff --git a/src/core/Comp.zig b/src/core/Comp.zig index d133277d7b4b5b8510c9427765b7d0613483737a..bf511722ceea1e75aee9457c1f78a7f90a7d8e0c 100644 --- a/src/core/Comp.zig +++ b/src/core/Comp.zig @@ -242,7 +242,6 @@ pub fn runCodegenJob(c: *Comp, value: InternPool.Value, air_const: Air) !void { defer arena_state.deinit(); const arena = arena_state.allocator(); - // TODO: put all this work on another thread if (c.options.dump_ir) { air_const.dumpToWriter(&c.intern_pool, c.out) catch {}; } @@ -667,7 +666,7 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N const Options = struct { dump_ir: bool = false, - verbose_sema: bool = false, + verbose_sema: bool = true, }; /// The compiler backend is stored separately from the compilation, and diff --git a/src/core/Sema.zig b/src/core/Sema.zig index 1980321f3df13b1830fd8307ca0c3c00cf541af3..0a8846387ce3325a24496a4ce1921abbf6894c12 100644 --- a/src/core/Sema.zig +++ b/src/core/Sema.zig @@ -852,7 +852,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) const initial_value = s.getRef(data.operand, .marked); - const runtime_known = try s.ensureValueRuntime(s.lazyNode(.operand), initial_value); + const runtime_known = try s.ensureRefRuntime(s.lazyNode(.operand), initial_value); return try s.addRef(.memory, .{ .init = runtime_known, @@ -1069,19 +1069,22 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) assert(s.frame.mode == .run_time); - const yes = s.getRef(if_data.yes, .no_mark); - const no = s.getRef(if_data.no, .no_mark); + var yes = s.getRef(if_data.yes, .no_mark); + var no = s.getRef(if_data.no, .no_mark); - const union_type = s.unionType(s.typeOfRef(yes), s.typeOfRef(no)); + if (yes.value()) |val| yes = (try s.ensureValueRuntime(s.lazyNode(.lhs), val)).toAir(); + if (no.value()) |val| no = (try s.ensureValueRuntime(s.lazyNode(.rhs), val)).toAir(); - // const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yes, union_type); - // const no_coerced = try s.coerceValue(s.lazyNode(.rhs), no, union_type); + const union_type = s.peerTypeResolution(&.{ s.typeOfRef(yes), s.typeOfRef(no) }); + + const yes_coerced = try s.coerceBranch(s.lazyNode(.lhs), yes, union_type); + const no_coerced = try s.coerceBranch(s.lazyNode(.rhs), no, union_type); s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{ .cond = s.getRef(if_data.cond, .no_mark), .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{ - .yes = yes, - .no = no, + .yes = yes_coerced, + .no = no_coerced, .union_ty = union_type, }), })); @@ -1243,7 +1246,10 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) // When both branches are values, Sema has nowhere // to jump to, so only one Air instruction is emitted - const union_type = s.unionType(yn[0].typeOf(s.ip), yn[1].typeOf(s.ip)); + yn[0] = try s.ensureValueRuntime(s.lazyNode(.lhs), yn[0]); + yn[1] = try s.ensureValueRuntime(s.lazyNode(.lhs), yn[1]); + + const union_type = s.peerTypeResolution(&.{ yn[0].typeOf(s.ip), yn[1].typeOf(s.ip) }); const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yn[0], union_type); const no_coerced = try s.coerceValue(s.lazyNode(.rhs), yn[1], union_type); @@ -1403,7 +1409,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) try s.ensureAtRuntime(s.lazyNode(.main_node)); for (scratch.slice(), 0..) |*ref, i| { - ref.* = try s.ensureValueRuntime(s.lazyNode(.argument(@intCast(i))), ref.*); + ref.* = try s.ensureRefRuntime(s.lazyNode(.argument(@intCast(i))), ref.*); } } @@ -1814,68 +1820,84 @@ fn coerceValue(s: *Sema, src: LazyNode, value: Value, to: Type) Error!Value { return s.coercionError(src, from, to); } -fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref { +fn coerceBranch(s: *Sema, src: LazyNode, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref { + return switch (ref.decode()) { + .index => |block| { + if (s.typeOfIndex(block) != to) return ref; + @panic("TODO"); + }, + .value => |value| (try s.coerceValue(src, value, to)).toAir(), + }; +} + +fn ensureValueRuntime(s: *Sema, src: LazyNode, value: Value) Error!Value { + const ty = value.typeOf(s.ip); + return switch (ty) { + // Allowed at runtime + .bool, + .float, + .int32, + .string, + .void, + .empty_tuple, + => value, + .numeric_literal => { + const float = value.toFloat(s.ip) orelse unreachable; + + std.debug.print("wtf:{d}\n", .{float}); + if (core.inInt32Range(float)) { + try s.report.warn(.numeric_int_var_ambiguity, .{ + .src = src, + }); + } + + return try s.makeValue(.{ + .float = float, + }); + }, + .numeric_literal_array => { + try s.report.warn(.numeric_int_array_var_ambiguity, .{ + .src = src, + }); + return try s.coerceArrayValue(src, value, .float); + }, + .never_array => { + if (value.valueData(s.ip, .array_value).get(s.ip).items_len != 0) { + return s.fail(.runtime_incompatible_type, .{ + .src = src, + .type = ty, + }); + } + return value; + }, + .type, + .never, + => return s.fail(.runtime_incompatible_type, .{ + .src = src, + .type = ty, + }), + .@"opaque" => @panic("TODO: represent comptime-known opaques better"), + _ => { + if (!ty.hasPossibleRepresentation(s.ip)) { + return s.fail(.runtime_incompatible_type, .{ + .src = src, + .type = ty, + }); + } + + // When a function is used as a runtime operand, it needs to + // have a resolved return type, so that calling it later is + // able to know what it returns. + _ = try s.resolveFunctionPrototype(value.toAir(), ty); + + return value; + }, + }; +} + +fn ensureRefRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref { switch (ref.decode()) { - .value => |v| { - const ty = v.typeOf(s.ip); - switch (ty) { - // Allowed at runtime - .bool, - .float, - .int32, - .string, - .void, - .empty_tuple, - => {}, - .numeric_literal => { - const float = v.toFloat(s.ip) orelse unreachable; - - if (!core.inInt32Range(float)) { - try s.report.warn(.numeric_int_var_ambiguity, .{ - .src = src, - }); - } - - return (try s.makeValue(.{ - .float = float, - })).toAir(); - }, - .numeric_literal_array => { - try s.report.warn(.numeric_int_array_var_ambiguity, .{ - .src = src, - }); - return (try s.coerceArrayValue(src, v, .float)).toAir(); - }, - .never_array => { - if (v.valueData(s.ip, .array_value).get(s.ip).items_len != 0) { - return s.fail(.runtime_incompatible_type, .{ - .src = src, - .type = ty, - }); - } - }, - .type, - .never, - => return s.fail(.runtime_incompatible_type, .{ - .src = src, - .type = ty, - }), - .@"opaque" => @panic("TODO: represent comptime-known opaques better"), - _ => { - if (!ty.hasPossibleRepresentation(s.ip)) { - return s.fail(.runtime_incompatible_type, .{ - .src = src, - .type = ty, - }); - } - - // When a function is used as a runtime operand, it needs to - // have a resolved return type, so that calling it later is - // able to know what it returns. - _ = try s.resolveFunctionPrototype(ref, ty); - }, - } - }, + .value => |v| return (try s.ensureValueRuntime(src, v)).toAir(), .index => { const ty = s.typeOfRef(ref); if (!ty.hasPossibleRepresentation(s.ip)) { @@ -1884,13 +1906,13 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst .type = ty, }); } + return ref; }, } - return ref; } fn ensureValueDefinedRepresentation(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref { - const runtime = try s.ensureValueRuntime(src, ref); + const runtime = try s.ensureRefRuntime(src, ref); const ty = s.typeOfRef(runtime); if (!ty.hasDefinedRepresentation(s.ip)) { return s.fail(.type_no_defined_representation, .{ @@ -1901,25 +1923,27 @@ fn ensureValueDefinedRepresentation(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) return runtime; } -fn unionType(s: *Sema, a: Type, b: Type) Type { - if (a == b) { - return a; +fn peerTypeResolution(s: *Sema, types: []const Type) Type { + var a = types[0]; + for (types[1..]) |b| { + if (a == b) { + continue; + } else if (b == .never) { + continue; + } else if (a == .never) { + a = b; + } else { + std.debug.panic( + "TODO: when two non-equal types implicitly merge (if/match), this should generate a union type.\na: {}\nb: {}", + .{ + a.dump(s.ip), + b.dump(s.ip), + }, + ); + return s.ip.get(s.gpa, .{ .@"union" = &.{ a, b } }); + } } - - if (a == .never) - return b; - - if (b == .never) - return a; - - std.debug.panic( - "TODO: when two non-equal types implicitly merge (if/match), this should generate a union type.\na: {}\nb: {}", - .{ - a.dump(s.ip), - b.dump(s.ip), - }, - ); - // return s.ip.get(s.gpa, .{ .@"union" = &.{ a, b } }); + return a; } fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Value { diff --git a/test/behavior/function b/test/behavior/function index 0ddad178342116254da0c255ccc29db2e2be0f1e..40ef62388235311f60f5221388864cca26a201f3 100644 --- a/test/behavior/function +++ b/test/behavior/function @@ -6,7 +6,7 @@ fn hello(n: int) -> int { if n > 1 { hello(n - 1) + 1 } else { - i + 1 } } -- 2.54.0