authorgravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-09-20 00:13:45-07:00
committergravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-09-20 00:13:45-07:00
log23604fa655b6a34c07acc49cf9b91f93193f705b
tree2450f4e6a52a62e3513a43dad7e47de6ad44f401
parent9f21c3c63e5825010c82063bee7b3d9164d6b8c2
signature Commit is signed but in an unrecognized format.

fix: coerce results when `if` expression branches merge


4 files changed, 114 insertions(+), 90 deletions(-)

CHANGELOG.md+1
......@@ -2,6 +2,7 @@
22
33- feat: allow any expression as a function body
44- fix: coerce `break` operands and implicit breaks to result type
5- fix: coerce results when `if` expression branches merge
56
67## 0.1.6
78
src/core/Comp.zig+1-2
......@@ -242,7 +242,6 @@ pub fn runCodegenJob(c: *Comp, value: InternPool.Value, air_const: Air) !void {
242242 defer arena_state.deinit();
243243 const arena = arena_state.allocator();
244244
245 // TODO: put all this work on another thread
246245 if (c.options.dump_ir) {
247246 air_const.dumpToWriter(&c.intern_pool, c.out) catch {};
248247 }
......@@ -667,7 +666,7 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N
667666
668667const Options = struct {
669668 dump_ir: bool = false,
670 verbose_sema: bool = false,
669 verbose_sema: bool = true,
671670};
672671
673672/// The compiler backend is stored separately from the compilation, and
src/core/Sema.zig+111-87
......@@ -852,7 +852,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
852852
853853 const initial_value = s.getRef(data.operand, .marked);
854854
855 const runtime_known = try s.ensureValueRuntime(s.lazyNode(.operand), initial_value);
855 const runtime_known = try s.ensureRefRuntime(s.lazyNode(.operand), initial_value);
856856
857857 return try s.addRef(.memory, .{
858858 .init = runtime_known,
......@@ -1069,19 +1069,22 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
10691069
10701070 assert(s.frame.mode == .run_time);
10711071
1072 const yes = s.getRef(if_data.yes, .no_mark);
1073 const no = s.getRef(if_data.no, .no_mark);
1072 var yes = s.getRef(if_data.yes, .no_mark);
1073 var no = s.getRef(if_data.no, .no_mark);
10741074
1075 const union_type = s.unionType(s.typeOfRef(yes), s.typeOfRef(no));
1075 if (yes.value()) |val| yes = (try s.ensureValueRuntime(s.lazyNode(.lhs), val)).toAir();
1076 if (no.value()) |val| no = (try s.ensureValueRuntime(s.lazyNode(.rhs), val)).toAir();
10761077
1077 // const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yes, union_type);
1078 // const no_coerced = try s.coerceValue(s.lazyNode(.rhs), no, union_type);
1078 const union_type = s.peerTypeResolution(&.{ s.typeOfRef(yes), s.typeOfRef(no) });
1079
1080 const yes_coerced = try s.coerceBranch(s.lazyNode(.lhs), yes, union_type);
1081 const no_coerced = try s.coerceBranch(s.lazyNode(.rhs), no, union_type);
10791082
10801083 s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{
10811084 .cond = s.getRef(if_data.cond, .no_mark),
10821085 .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{
1083 .yes = yes,
1084 .no = no,
1086 .yes = yes_coerced,
1087 .no = no_coerced,
10851088 .union_ty = union_type,
10861089 }),
10871090 }));
......@@ -1243,7 +1246,10 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
12431246
12441247 // When both branches are values, Sema has nowhere
12451248 // to jump to, so only one Air instruction is emitted
1246 const union_type = s.unionType(yn[0].typeOf(s.ip), yn[1].typeOf(s.ip));
1249 yn[0] = try s.ensureValueRuntime(s.lazyNode(.lhs), yn[0]);
1250 yn[1] = try s.ensureValueRuntime(s.lazyNode(.lhs), yn[1]);
1251
1252 const union_type = s.peerTypeResolution(&.{ yn[0].typeOf(s.ip), yn[1].typeOf(s.ip) });
12471253
12481254 const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yn[0], union_type);
12491255 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)
14031409 try s.ensureAtRuntime(s.lazyNode(.main_node));
14041410
14051411 for (scratch.slice(), 0..) |*ref, i| {
1406 ref.* = try s.ensureValueRuntime(s.lazyNode(.argument(@intCast(i))), ref.*);
1412 ref.* = try s.ensureRefRuntime(s.lazyNode(.argument(@intCast(i))), ref.*);
14071413 }
14081414 }
14091415
......@@ -1814,68 +1820,84 @@ fn coerceValue(s: *Sema, src: LazyNode, value: Value, to: Type) Error!Value {
18141820 return s.coercionError(src, from, to);
18151821}
18161822
1817fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref {
1818 switch (ref.decode()) {
1819 .value => |v| {
1820 const ty = v.typeOf(s.ip);
1821 switch (ty) {
1822 // Allowed at runtime
1823 .bool,
1824 .float,
1825 .int32,
1826 .string,
1827 .void,
1828 .empty_tuple,
1829 => {},
1830 .numeric_literal => {
1831 const float = v.toFloat(s.ip) orelse unreachable;
1832
1833 if (!core.inInt32Range(float)) {
1834 try s.report.warn(.numeric_int_var_ambiguity, .{
1835 .src = src,
1836 });
1837 }
1823fn coerceBranch(s: *Sema, src: LazyNode, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {
1824 return switch (ref.decode()) {
1825 .index => |block| {
1826 if (s.typeOfIndex(block) != to) return ref;
1827 @panic("TODO");
1828 },
1829 .value => |value| (try s.coerceValue(src, value, to)).toAir(),
1830 };
1831}
18381832
1839 return (try s.makeValue(.{
1840 .float = float,
1841 })).toAir();
1842 },
1843 .numeric_literal_array => {
1844 try s.report.warn(.numeric_int_array_var_ambiguity, .{
1845 .src = src,
1846 });
1847 return (try s.coerceArrayValue(src, v, .float)).toAir();
1848 },
1849 .never_array => {
1850 if (v.valueData(s.ip, .array_value).get(s.ip).items_len != 0) {
1851 return s.fail(.runtime_incompatible_type, .{
1852 .src = src,
1853 .type = ty,
1854 });
1855 }
1856 },
1857 .type,
1858 .never,
1859 => return s.fail(.runtime_incompatible_type, .{
1833fn ensureValueRuntime(s: *Sema, src: LazyNode, value: Value) Error!Value {
1834 const ty = value.typeOf(s.ip);
1835 return switch (ty) {
1836 // Allowed at runtime
1837 .bool,
1838 .float,
1839 .int32,
1840 .string,
1841 .void,
1842 .empty_tuple,
1843 => value,
1844 .numeric_literal => {
1845 const float = value.toFloat(s.ip) orelse unreachable;
1846
1847 std.debug.print("wtf:{d}\n", .{float});
1848 if (core.inInt32Range(float)) {
1849 try s.report.warn(.numeric_int_var_ambiguity, .{
18601850 .src = src,
1861 .type = ty,
1862 }),
1863 .@"opaque" => @panic("TODO: represent comptime-known opaques better"),
1864 _ => {
1865 if (!ty.hasPossibleRepresentation(s.ip)) {
1866 return s.fail(.runtime_incompatible_type, .{
1867 .src = src,
1868 .type = ty,
1869 });
1870 }
1851 });
1852 }
18711853
1872 // When a function is used as a runtime operand, it needs to
1873 // have a resolved return type, so that calling it later is
1874 // able to know what it returns.
1875 _ = try s.resolveFunctionPrototype(ref, ty);
1876 },
1854 return try s.makeValue(.{
1855 .float = float,
1856 });
1857 },
1858 .numeric_literal_array => {
1859 try s.report.warn(.numeric_int_array_var_ambiguity, .{
1860 .src = src,
1861 });
1862 return try s.coerceArrayValue(src, value, .float);
1863 },
1864 .never_array => {
1865 if (value.valueData(s.ip, .array_value).get(s.ip).items_len != 0) {
1866 return s.fail(.runtime_incompatible_type, .{
1867 .src = src,
1868 .type = ty,
1869 });
18771870 }
1871 return value;
18781872 },
1873 .type,
1874 .never,
1875 => return s.fail(.runtime_incompatible_type, .{
1876 .src = src,
1877 .type = ty,
1878 }),
1879 .@"opaque" => @panic("TODO: represent comptime-known opaques better"),
1880 _ => {
1881 if (!ty.hasPossibleRepresentation(s.ip)) {
1882 return s.fail(.runtime_incompatible_type, .{
1883 .src = src,
1884 .type = ty,
1885 });
1886 }
1887
1888 // When a function is used as a runtime operand, it needs to
1889 // have a resolved return type, so that calling it later is
1890 // able to know what it returns.
1891 _ = try s.resolveFunctionPrototype(value.toAir(), ty);
1892
1893 return value;
1894 },
1895 };
1896}
1897
1898fn ensureRefRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref {
1899 switch (ref.decode()) {
1900 .value => |v| return (try s.ensureValueRuntime(src, v)).toAir(),
18791901 .index => {
18801902 const ty = s.typeOfRef(ref);
18811903 if (!ty.hasPossibleRepresentation(s.ip)) {
......@@ -1884,13 +1906,13 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst
18841906 .type = ty,
18851907 });
18861908 }
1909 return ref;
18871910 },
18881911 }
1889 return ref;
18901912}
18911913
18921914fn ensureValueDefinedRepresentation(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref {
1893 const runtime = try s.ensureValueRuntime(src, ref);
1915 const runtime = try s.ensureRefRuntime(src, ref);
18941916 const ty = s.typeOfRef(runtime);
18951917 if (!ty.hasDefinedRepresentation(s.ip)) {
18961918 return s.fail(.type_no_defined_representation, .{
......@@ -1901,25 +1923,27 @@ fn ensureValueDefinedRepresentation(s: *Sema, src: LazyNode, ref: Air.Inst.Ref)
19011923 return runtime;
19021924}
19031925
1904fn unionType(s: *Sema, a: Type, b: Type) Type {
1905 if (a == b) {
1906 return a;
1926fn peerTypeResolution(s: *Sema, types: []const Type) Type {
1927 var a = types[0];
1928 for (types[1..]) |b| {
1929 if (a == b) {
1930 continue;
1931 } else if (b == .never) {
1932 continue;
1933 } else if (a == .never) {
1934 a = b;
1935 } else {
1936 std.debug.panic(
1937 "TODO: when two non-equal types implicitly merge (if/match), this should generate a union type.\na: {}\nb: {}",
1938 .{
1939 a.dump(s.ip),
1940 b.dump(s.ip),
1941 },
1942 );
1943 return s.ip.get(s.gpa, .{ .@"union" = &.{ a, b } });
1944 }
19071945 }
1908
1909 if (a == .never)
1910 return b;
1911
1912 if (b == .never)
1913 return a;
1914
1915 std.debug.panic(
1916 "TODO: when two non-equal types implicitly merge (if/match), this should generate a union type.\na: {}\nb: {}",
1917 .{
1918 a.dump(s.ip),
1919 b.dump(s.ip),
1920 },
1921 );
1922 // return s.ip.get(s.gpa, .{ .@"union" = &.{ a, b } });
1946 return a;
19231947}
19241948
19251949fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Value {
test/behavior/function+1-1
......@@ -6,7 +6,7 @@ fn hello(n: int) -> int {
66 if n > 1 {
77 hello(n - 1) + 1
88 } else {
9 i
9 1
1010 }
1111}
1212