10 files changed, 220 insertions(+), 25 deletions(-)
CHANGELOG.md+3| ... | @@ -3,8 +3,11 @@ | ... | @@ -3,8 +3,11 @@ |
| 3 | - chore: rename the language to "ix" | 3 | - chore: rename the language to "ix" |
| 4 | - feat: language manual gets a pleasing redesign | 4 | - feat: language manual gets a pleasing redesign |
| 5 | - feat: allow any expression as a function body | 5 | - feat: allow any expression as a function body |
| 6 | - feat: add `assert` keyword | ||
| 6 | - fix: coerce `break` operands and implicit breaks to result type | 7 | - fix: coerce `break` operands and implicit breaks to result type |
| 7 | - fix: coerce results when `if` expression branches merge | 8 | - fix: coerce results when `if` expression branches merge |
| 9 | - fix: ensure if branch results to a runtime value when the condition is not compile-time known | ||
| 10 | - fix(js): stop printing redundant parenthesis on ternary expressions | ||
| 8 | 11 | ||
| 9 | ## 0.1.6 | 12 | ## 0.1.6 |
| 10 | 13 |
build.zig+1-1| ... | @@ -121,7 +121,7 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { | ... | @@ -121,7 +121,7 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { |
| 121 | 121 | ||
| 122 | // Main CLI | 122 | // Main CLI |
| 123 | const cli = b.addExecutable(.{ | 123 | const cli = b.addExecutable(.{ |
| 124 | .name = "compiler", | 124 | .name = "ix", |
| 125 | .root_source_file = b.path("src/main.zig"), | 125 | .root_source_file = b.path("src/main.zig"), |
| 126 | .target = options.target, | 126 | .target = options.target, |
| 127 | .optimize = options.optimize, | 127 | .optimize = options.optimize, |
src/core/Comp.zig+18-1| ... | @@ -203,7 +203,12 @@ fn runAnalysisJob(comp: *Comp, unit: InternPool.AnalysisUnit, first_blocked_sema | ... | @@ -203,7 +203,12 @@ fn runAnalysisJob(comp: *Comp, unit: InternPool.AnalysisUnit, first_blocked_sema |
| 203 | => |e| return e, | 203 | => |e| return e, |
| 204 | 204 | ||
| 205 | error.AnalysisFail => |e| { | 205 | error.AnalysisFail => |e| { |
| 206 | const report = try sema.report.finalize(); | 206 | const report = sema.report.finalize() catch { |
| 207 | comp.ongoing_analysis.removeByPtr(gop.key_ptr); | ||
| 208 | _ = sema.deinit(.free_all); // TODO: arena reuse | ||
| 209 | comp.gpa.destroy(sema); | ||
| 210 | return e; | ||
| 211 | }; | ||
| 207 | defer report.deinit(comp.gpa); | 212 | defer report.deinit(comp.gpa); |
| 208 | 213 | ||
| 209 | report.dump(.{ .comp = .{ .comp = comp, .file = sema.file } }, comp.out) catch {}; | 214 | report.dump(.{ .comp = .{ .comp = comp, .file = sema.file } }, comp.out) catch {}; |
| ... | @@ -216,6 +221,18 @@ fn runAnalysisJob(comp: *Comp, unit: InternPool.AnalysisUnit, first_blocked_sema | ... | @@ -216,6 +221,18 @@ fn runAnalysisJob(comp: *Comp, unit: InternPool.AnalysisUnit, first_blocked_sema |
| 216 | }, | 221 | }, |
| 217 | }; | 222 | }; |
| 218 | 223 | ||
| 224 | errdefer { | ||
| 225 | comp.ongoing_analysis.removeByPtr(gop.key_ptr); | ||
| 226 | _ = sema.deinit(.free_all); // TODO: arena reuse | ||
| 227 | comp.gpa.destroy(sema); | ||
| 228 | } | ||
| 229 | |||
| 230 | const report = try sema.report.finalize(); | ||
| 231 | defer report.deinit(comp.gpa); // TODO: store warnings | ||
| 232 | if (report.warnings.len > 0) { | ||
| 233 | report.dump(.{ .comp = .{ .comp = comp, .file = sema.file } }, comp.out) catch {}; | ||
| 234 | } | ||
| 235 | |||
| 219 | switch (unit.unwrap()) { | 236 | switch (unit.unwrap()) { |
| 220 | .decl => |index| { | 237 | .decl => |index| { |
| 221 | comp.intern_pool.declPtr(index).analysis = .complete; | 238 | comp.intern_pool.declPtr(index).analysis = .complete; |
src/core/Hir.Dump.zig+6-1| ... | @@ -219,13 +219,18 @@ const State = struct { | ... | @@ -219,13 +219,18 @@ const State = struct { |
| 219 | .compile_error, | 219 | .compile_error, |
| 220 | .type_of, | 220 | .type_of, |
| 221 | .array_type, | 221 | .array_type, |
| 222 | .assert, | ||
| 223 | => |comptime_tag| { | 222 | => |comptime_tag| { |
| 224 | const data = hir.instData(comptime_tag, inst); | 223 | const data = hir.instData(comptime_tag, inst); |
| 225 | try writer.writeAll("("); | 224 | try writer.writeAll("("); |
| 226 | try s.dumpRefMarker(writer, data.operand); | 225 | try s.dumpRefMarker(writer, data.operand); |
| 227 | try writer.writeAll(")"); | 226 | try writer.writeAll(")"); |
| 228 | }, | 227 | }, |
| 228 | .assert => { | ||
| 229 | const data = hir.instData(.assert, inst); | ||
| 230 | try writer.writeAll("("); | ||
| 231 | try s.dumpRef(writer, data.operand); | ||
| 232 | try writer.writeAll(")"); | ||
| 233 | }, | ||
| 229 | .call => { | 234 | .call => { |
| 230 | const call: Hir.Call = hir.instData(.call, inst).extra_index.get(hir); | 235 | const call: Hir.Call = hir.instData(.call, inst).extra_index.get(hir); |
| 231 | try writer.writeAll("("); | 236 | try writer.writeAll("("); |
src/core/Hir.zig+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | //! expressions as flat instruction sequences. | 6 | //! expressions as flat instruction sequences. |
| 7 | //! | 7 | //! |
| 8 | //! Hir for a given file has no dependencies on it's imports, meaning it can be | 8 | //! Hir for a given file has no dependencies on it's imports, meaning it can be |
| 9 | //! computed in parallel and cached when not changed. | 9 | //! computed in parallel and then cached when not changed. |
| 10 | //! | 10 | //! |
| 11 | //! A helper program exists to visualize Hir: './build transform -- ./file.ix' | 11 | //! A helper program exists to visualize Hir: './build transform -- ./file.ix' |
| 12 | //! Alternatively, `ix build` has the `--dump-ir` option. | 12 | //! Alternatively, `ix build` has the `--dump-ir` option. |
src/core/InternPool.zig+21| ... | @@ -410,6 +410,27 @@ pub const Value = enum(u32) { | ... | @@ -410,6 +410,27 @@ pub const Value = enum(u32) { |
| 410 | else => null, | 410 | else => null, |
| 411 | }; | 411 | }; |
| 412 | } | 412 | } |
| 413 | |||
| 414 | pub fn displayName(value: Value, ip: *const InternPool) std.fmt.Formatter(formatDisplayName) { | ||
| 415 | return .{ .data = .{ .value = value, .ip = ip } }; | ||
| 416 | } | ||
| 417 | |||
| 418 | fn formatDisplayName( | ||
| 419 | ctx: struct { value: Value, ip: *const InternPool }, | ||
| 420 | comptime _: []const u8, | ||
| 421 | _: std.fmt.FormatOptions, | ||
| 422 | writer: anytype, | ||
| 423 | ) !void { | ||
| 424 | const value = ctx.value; | ||
| 425 | const ip = ctx.ip; | ||
| 426 | const key = value.toKey(ip); | ||
| 427 | switch (key) { | ||
| 428 | inline .comptime_float, .int32, .float => |n| { | ||
| 429 | try writer.print("{d}", .{n}); | ||
| 430 | }, | ||
| 431 | else => |tag| std.debug.panic("TODO: print value .{s}", .{@tagName(tag)}), | ||
| 432 | } | ||
| 433 | } | ||
| 413 | }; | 434 | }; |
| 414 | 435 | ||
| 415 | /// Type is subset of Value which represents the values that have typeOf() == Value.type_type | 436 | /// Type is subset of Value which represents the values that have typeOf() == Value.type_type |
src/core/Report.zig+61-1| ... | @@ -56,6 +56,52 @@ pub const Err = union(enum) { | ... | @@ -56,6 +56,52 @@ pub const Err = union(enum) { |
| 56 | try ctx.msgNode(try data.src.get(ctx), "value must be known at compile-time", .{}); | 56 | try ctx.msgNode(try data.src.get(ctx), "value must be known at compile-time", .{}); |
| 57 | } | 57 | } |
| 58 | }, | 58 | }, |
| 59 | user_assertion_basic: struct { | ||
| 60 | src: LazyNode, | ||
| 61 | |||
| 62 | fn build(data: @This(), ctx: *MsgContext) !void { | ||
| 63 | try ctx.msgNode(try data.src.get(ctx), "assertion failure", .{}); | ||
| 64 | } | ||
| 65 | }, | ||
| 66 | user_assertion_bin: struct { | ||
| 67 | src: LazyNode, | ||
| 68 | left: Value, | ||
| 69 | right: Value, | ||
| 70 | |||
| 71 | fn build(data: @This(), ctx: *MsgContext) !void { | ||
| 72 | const node = try data.src.get(ctx); | ||
| 73 | try ctx.msgNode(node, "assertion failure: {} {s} {}", .{ | ||
| 74 | fmtAssertionOperand(data.left, ctx.ip()), | ||
| 75 | switch (ctx.ast.nodeTag(node)) { | ||
| 76 | .eq => "==", | ||
| 77 | .ne => "!=", | ||
| 78 | .lt => "<", | ||
| 79 | .le => "<=", | ||
| 80 | .gt => ">", | ||
| 81 | .ge => ">=", | ||
| 82 | else => unreachable, | ||
| 83 | }, | ||
| 84 | fmtAssertionOperand(data.right, ctx.ip()), | ||
| 85 | }); | ||
| 86 | } | ||
| 87 | |||
| 88 | fn fmtAssertionOperand(v: Value, ip: *const InternPool) std.fmt.Formatter(fmtAssertionOperandImpl) { | ||
| 89 | return .{ .data = .{ .value = v, .ip = ip } }; | ||
| 90 | } | ||
| 91 | |||
| 92 | fn fmtAssertionOperandImpl( | ||
| 93 | data: struct { value: Value, ip: *const InternPool }, | ||
| 94 | comptime _: []const u8, | ||
| 95 | _: std.fmt.FormatOptions, | ||
| 96 | w: anytype, | ||
| 97 | ) !void { | ||
| 98 | if (data.value.toType(data.ip)) |t| { | ||
| 99 | try w.print("any {}", .{t.displayName(data.ip)}); | ||
| 100 | } else { | ||
| 101 | try w.print("{}", .{data.value.displayName(data.ip)}); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | }, | ||
| 59 | cannot_be_comptime: struct { | 105 | cannot_be_comptime: struct { |
| 60 | src: LazyNode, | 106 | src: LazyNode, |
| 61 | 107 | ||
| ... | @@ -538,7 +584,8 @@ pub const Warn = union(enum) { | ... | @@ -538,7 +584,8 @@ pub const Warn = union(enum) { |
| 538 | 584 | ||
| 539 | fn build(data: @This(), ctx: *MsgContext) !void { | 585 | fn build(data: @This(), ctx: *MsgContext) !void { |
| 540 | const node = try data.src.get(ctx); | 586 | const node = try data.src.get(ctx); |
| 541 | try ctx.msgNode(node, "constant integer cannot be represented as a runtime value, implicitly casting to \"float\"", .{}); | 587 | try ctx.msgNode(node, "constant numeric values at runtime needs a result type, implicitly casting to \"float\"", .{}); |
| 588 | // TODO: trace to the result type and point at it | ||
| 542 | try ctx.msgAlone("add \": float\" or \": int\" after the variable name to set the type", .{}); | 589 | try ctx.msgAlone("add \": float\" or \": int\" after the variable name to set the type", .{}); |
| 543 | } | 590 | } |
| 544 | }, | 591 | }, |
| ... | @@ -1206,6 +1253,19 @@ pub const LazyNode = struct { | ... | @@ -1206,6 +1253,19 @@ pub const LazyNode = struct { |
| 1206 | .field => { | 1253 | .field => { |
| 1207 | return base_node; | 1254 | return base_node; |
| 1208 | }, | 1255 | }, |
| 1256 | .@"if" => { | ||
| 1257 | assert(node_tag == .@"if"); | ||
| 1258 | switch (lazy.operand) { | ||
| 1259 | .operand => return data.@"if".cond, | ||
| 1260 | .lhs => return ctx.ast.extraData(data.@"if".extra, Ast.If).yes, | ||
| 1261 | .rhs => return ctx.ast.extraData(data.@"if".extra, Ast.If).no.unwrap().?, | ||
| 1262 | else => {}, | ||
| 1263 | } | ||
| 1264 | }, | ||
| 1265 | .assert => { | ||
| 1266 | assert(node_tag == .assert); | ||
| 1267 | return data.un.expr; | ||
| 1268 | }, | ||
| 1209 | else => {}, | 1269 | else => {}, |
| 1210 | } | 1270 | } |
| 1211 | 1271 |
src/core/Sema.zig+75-9| ... | @@ -490,19 +490,23 @@ const InstructionKind = enum { | ... | @@ -490,19 +490,23 @@ const InstructionKind = enum { |
| 490 | pub inline fn fromTag(tag: Hir.Tag) InstructionKind { | 490 | pub inline fn fromTag(tag: Hir.Tag) InstructionKind { |
| 491 | return switch (tag) { | 491 | return switch (tag) { |
| 492 | // these push new frames, and frame end will assign its value | 492 | // these push new frames, and frame end will assign its value |
| 493 | .func, | ||
| 494 | .block, | ||
| 495 | .@"comptime", | 493 | .@"comptime", |
| 496 | .@"if", | 494 | .@"if", |
| 495 | .assert, | ||
| 496 | .block, | ||
| 497 | .func, | ||
| 497 | => .control_flow, | 498 | => .control_flow, |
| 499 | |||
| 498 | // break is used to pop a frame, which has the | 500 | // break is used to pop a frame, which has the |
| 499 | // possibility to end analysis | 501 | // possibility to end analysis |
| 500 | .@"break", | 502 | .@"break", |
| 501 | => .pop_control_flow, | 503 | => .pop_control_flow, |
| 504 | |||
| 502 | // these are statements which do not evaluate to anything (void) | 505 | // these are statements which do not evaluate to anything (void) |
| 503 | .discard, | 506 | .discard, |
| 504 | .ensure_void, | 507 | .ensure_void, |
| 505 | => .void, | 508 | => .void, |
| 509 | |||
| 506 | // all other instructions are single-visit, returning a value | 510 | // all other instructions are single-visit, returning a value |
| 507 | else => .result, | 511 | else => .result, |
| 508 | }; | 512 | }; |
| ... | @@ -521,14 +525,14 @@ const PopControlFlowState = enum { stop, @"continue" }; | ... | @@ -521,14 +525,14 @@ const PopControlFlowState = enum { stop, @"continue" }; |
| 521 | 525 | ||
| 522 | fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag).Result() { | 526 | fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag).Result() { |
| 523 | switch (tag) { | 527 | switch (tag) { |
| 524 | .block => unreachable, // frame must already be pushed | 528 | .block, |
| 529 | => unreachable, // frame must already be pushed | ||
| 530 | |||
| 525 | .declaration, | 531 | .declaration, |
| 526 | .variable_declaration, | 532 | .variable_declaration, |
| 527 | .test_declaration, | 533 | .test_declaration, |
| 528 | => unreachable, // declarations are not analzed by Sema | 534 | => unreachable, // declarations are not analzed by Sema |
| 529 | 535 | ||
| 530 | // TODO: pre-evaluate `.param` nodes, since they are always at the start of a function. | ||
| 531 | // this way we can avoid repeating the non-trivial call into InternPool | ||
| 532 | .param => { | 536 | .param => { |
| 533 | const param: Hir.DataNode = s.frame.currentData(.param); | 537 | const param: Hir.DataNode = s.frame.currentData(.param); |
| 534 | assert(s.frame.mode == .run_time); // never emitted in a comptime block | 538 | assert(s.frame.mode == .run_time); // never emitted in a comptime block |
| ... | @@ -909,9 +913,24 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -909,9 +913,24 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 909 | }); | 913 | }); |
| 910 | }, | 914 | }, |
| 911 | 915 | ||
| 912 | // .assert => { | 916 | .assert => { |
| 913 | // return Value.void.toAir(); | 917 | const operand = s.frame.currentData(.assert).operand; |
| 914 | // }, | 918 | switch (operand.decode()) { |
| 919 | .value => |val| { | ||
| 920 | const coerced = try s.coerceValue(s.lazyNode(.operand), val, .bool); | ||
| 921 | if (coerced == .bool_false) { | ||
| 922 | return s.fail(.user_assertion_basic, .{ .src = s.lazyNode(.operand) }); | ||
| 923 | } | ||
| 924 | s.frame.advance(); | ||
| 925 | return; | ||
| 926 | }, | ||
| 927 | .index => |index| { | ||
| 928 | const mode = s.frame.mode; | ||
| 929 | try s.pushFrame(); | ||
| 930 | s.frame = Frame.initFromBlock(s, index, mode, .none); | ||
| 931 | }, | ||
| 932 | } | ||
| 933 | }, | ||
| 915 | 934 | ||
| 916 | .@"comptime" => { | 935 | .@"comptime" => { |
| 917 | const index = s.frame.currentIndex(); | 936 | const index = s.frame.currentIndex(); |
| ... | @@ -1031,6 +1050,54 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1031,6 +1050,54 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1031 | s.inst_map.add(s.frame.currentIndex(), operand); | 1050 | s.inst_map.add(s.frame.currentIndex(), operand); |
| 1032 | s.frame.advance(); | 1051 | s.frame.advance(); |
| 1033 | }, | 1052 | }, |
| 1053 | .assert => { | ||
| 1054 | const coerced = try s.coerceRef(s.lazyNode(.operand), operand, .bool); | ||
| 1055 | switch (coerced.decode()) { | ||
| 1056 | .value => |val| switch (val) { | ||
| 1057 | .bool_true => {}, // pass | ||
| 1058 | .bool_false => { | ||
| 1059 | // Scan for the pattern: | ||
| 1060 | // %n = assert(%n-3 = block [ | ||
| 1061 | // ... | ||
| 1062 | // %n-4 = eq(...) | ||
| 1063 | // %n-2 = coercion(%bool_type, ...) | ||
| 1064 | // %n-1 = break(%x, %n-2) | ||
| 1065 | // ]) | ||
| 1066 | const cmp_index: Hir.Inst.Index = @enumFromInt(@intFromEnum(s.frame.currentIndex()) - 4); | ||
| 1067 | switch (s.hir.instTag(cmp_index)) { | ||
| 1068 | .eq, | ||
| 1069 | .ne, | ||
| 1070 | .lt, | ||
| 1071 | .le, | ||
| 1072 | .gt, | ||
| 1073 | .ge, | ||
| 1074 | => { | ||
| 1075 | const cmp_inst = s.hir.instDataUnsafe(Hir.PlNode(Hir.Bin), cmp_index); | ||
| 1076 | const cmp_pl: Hir.Bin = cmp_inst.extra_index.get(&s.hir); | ||
| 1077 | return s.fail(.user_assertion_bin, .{ | ||
| 1078 | .src = s.lazyNode(.operand), | ||
| 1079 | .left = switch (s.getRef(cmp_pl.lhs, .no_mark).decode()) { | ||
| 1080 | .value => |v| v, | ||
| 1081 | .index => @panic("TODO"), | ||
| 1082 | }, | ||
| 1083 | .right = switch (s.getRef(cmp_pl.rhs, .no_mark).decode()) { | ||
| 1084 | .value => |v| v, | ||
| 1085 | .index => @panic("TODO"), | ||
| 1086 | }, | ||
| 1087 | }); | ||
| 1088 | }, | ||
| 1089 | else => {}, | ||
| 1090 | } | ||
| 1091 | return s.fail(.user_assertion_basic, .{ .src = s.lazyNode(.operand) }); | ||
| 1092 | }, | ||
| 1093 | else => unreachable, | ||
| 1094 | }, | ||
| 1095 | .index => { | ||
| 1096 | @panic("TODO"); | ||
| 1097 | }, | ||
| 1098 | } | ||
| 1099 | s.frame.advance(); | ||
| 1100 | }, | ||
| 1034 | .@"if" => { | 1101 | .@"if" => { |
| 1035 | const if_data: Hir.If = s.frame.currentPayload(.@"if"); | 1102 | const if_data: Hir.If = s.frame.currentPayload(.@"if"); |
| 1036 | 1103 | ||
| ... | @@ -1842,7 +1909,6 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, value: Value) Error!Value { | ... | @@ -1842,7 +1909,6 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, value: Value) Error!Value { |
| 1842 | .numeric_literal => { | 1909 | .numeric_literal => { |
| 1843 | const float = value.toFloat(s.ip) orelse unreachable; | 1910 | const float = value.toFloat(s.ip) orelse unreachable; |
| 1844 | 1911 | ||
| 1845 | std.debug.print("wtf:{d}\n", .{float}); | ||
| 1846 | if (core.inInt32Range(float)) { | 1912 | if (core.inInt32Range(float)) { |
| 1847 | try s.report.warn(.numeric_int_var_ambiguity, .{ | 1913 | try s.report.warn(.numeric_int_var_ambiguity, .{ |
| 1848 | .src = src, | 1914 | .src = src, |
src/core/Transform.zig+29-9| ... | @@ -153,17 +153,13 @@ const ResultType = union(enum) { | ... | @@ -153,17 +153,13 @@ const ResultType = union(enum) { |
| 153 | 153 | ||
| 154 | const BlockContext = struct { | 154 | const BlockContext = struct { |
| 155 | decl_node_offset: i32, | 155 | decl_node_offset: i32, |
| 156 | return_target: ?*ReturnTarget = null, | ||
| 157 | break_target: ?*BreakTarget = null, | 156 | break_target: ?*BreakTarget = null, |
| 158 | 157 | return_target: ?*const ReturnTarget = null, | |
| 159 | const ReturnTarget = struct { | ||
| 160 | block_index: Inst.Index, | ||
| 161 | result_type: ResultType, | ||
| 162 | }; | ||
| 163 | 158 | ||
| 164 | /// This struct is always stored on the stack; the idea is to pass mutable | 159 | /// This struct is always stored on the stack; the idea is to pass mutable |
| 165 | /// pointers to it. When `break :brk` is encountered, it will generate the | 160 | /// pointers to it. When `break :brk` is encountered, it will generate the |
| 166 | /// block instruction. This way, redundant blocks are never created. | 161 | /// block instruction and save it in `block_index`. This way, the caller |
| 162 | /// can avoid having a Hir block instruction if it wont be used. | ||
| 167 | const BreakTarget = struct { | 163 | const BreakTarget = struct { |
| 168 | label: ?[]const u8, | 164 | label: ?[]const u8, |
| 169 | /// This allows `continue` as well as implicit break targeting. | 165 | /// This allows `continue` as well as implicit break targeting. |
| ... | @@ -182,6 +178,14 @@ const BlockContext = struct { | ... | @@ -182,6 +178,14 @@ const BlockContext = struct { |
| 182 | } | 178 | } |
| 183 | }; | 179 | }; |
| 184 | 180 | ||
| 181 | /// Same strategy as `BreakTarget`, but it is not mutated. A function's | ||
| 182 | /// block is always generated first so that it's index is the first of it's | ||
| 183 | /// contents. | ||
| 184 | const ReturnTarget = struct { | ||
| 185 | block_index: Inst.Index, | ||
| 186 | result_type: ResultType, | ||
| 187 | }; | ||
| 188 | |||
| 185 | fn init(root_node: NodeIndex) BlockContext { | 189 | fn init(root_node: NodeIndex) BlockContext { |
| 186 | return .{ | 190 | return .{ |
| 187 | .decl_node_offset = @intCast(root_node), | 191 | .decl_node_offset = @intCast(root_node), |
| ... | @@ -198,7 +202,7 @@ const BlockContext = struct { | ... | @@ -198,7 +202,7 @@ const BlockContext = struct { |
| 198 | }; | 202 | }; |
| 199 | } | 203 | } |
| 200 | 204 | ||
| 201 | fn withReturn(self: BlockContext, return_target: ?*ReturnTarget) BlockContext { | 205 | fn withReturn(self: BlockContext, return_target: ?*const ReturnTarget) BlockContext { |
| 202 | return .{ | 206 | return .{ |
| 203 | .decl_node_offset = self.decl_node_offset, | 207 | .decl_node_offset = self.decl_node_offset, |
| 204 | .return_target = return_target, | 208 | .return_target = return_target, |
| ... | @@ -1113,7 +1117,6 @@ fn transformExpression( | ... | @@ -1113,7 +1117,6 @@ fn transformExpression( |
| 1113 | .bool_not, | 1117 | .bool_not, |
| 1114 | .bit_not, | 1118 | .bit_not, |
| 1115 | .array_type, | 1119 | .array_type, |
| 1116 | .assert, | ||
| 1117 | => |tag| { | 1120 | => |tag| { |
| 1118 | const ast_un = t.ast.nodeData(node).un; | 1121 | const ast_un = t.ast.nodeData(node).un; |
| 1119 | const teg_tag = @field(Hir.Tag, @tagName(tag)); | 1122 | const teg_tag = @field(Hir.Tag, @tagName(tag)); |
| ... | @@ -1154,6 +1157,23 @@ fn transformExpression( | ... | @@ -1154,6 +1157,23 @@ fn transformExpression( |
| 1154 | return operand; | 1157 | return operand; |
| 1155 | }, | 1158 | }, |
| 1156 | 1159 | ||
| 1160 | .assert => { | ||
| 1161 | const ast_un = t.ast.nodeData(node).un; | ||
| 1162 | const operand = try t.transformToBlock(ctx, ast_un.expr, .untracked_result, .{ .ref = .bool_type }, .run_time); | ||
| 1163 | // Move the source index to the `comptime` node | ||
| 1164 | if (operand.index()) |index| { | ||
| 1165 | const slice = t.insts.slice(); | ||
| 1166 | const data: *EncodedData = &slice.items(.data)[@intFromEnum(index)]; | ||
| 1167 | var existing = data.decode(.block, slice.items(.tag)[@intFromEnum(index)]); | ||
| 1168 | existing.src_node = ctx.nodeOffset(node); | ||
| 1169 | data.* = .encode(.block, existing); | ||
| 1170 | } | ||
| 1171 | return try t.addRef(.assert, .{ | ||
| 1172 | .src_node = ctx.nodeOffset(node), | ||
| 1173 | .operand = operand, | ||
| 1174 | }); | ||
| 1175 | }, | ||
| 1176 | |||
| 1157 | .label => { | 1177 | .label => { |
| 1158 | const expr = t.ast.nodeData(node).label.expr; | 1178 | const expr = t.ast.nodeData(node).label.expr; |
| 1159 | const label = t.ast.tokenSlice(t.ast.nodeToken(node)); | 1179 | const label = t.ast.tokenSlice(t.ast.nodeToken(node)); |
src/core/js/Link.zig+5-2| ... | @@ -480,13 +480,16 @@ fn printExprLevel( | ... | @@ -480,13 +480,16 @@ fn printExprLevel( |
| 480 | .ternary => { | 480 | .ternary => { |
| 481 | const ternary: Tree.Ternary = tree.nodeData(.ternary, node); | 481 | const ternary: Tree.Ternary = tree.nodeData(.ternary, node); |
| 482 | const data = ternary.data.get(tree); | 482 | const data = ternary.data.get(tree); |
| 483 | try writer.writeAll("("); | 483 | const wrap = Level.conditional.needsParens(level); |
| 484 | if (wrap) | ||
| 485 | try writer.writeAll("("); | ||
| 484 | try link.printExpr(tree, ternary.cond, writer); | 486 | try link.printExpr(tree, ternary.cond, writer); |
| 485 | try writer.writeAll(" ? "); | 487 | try writer.writeAll(" ? "); |
| 486 | try link.printExpr(tree, data.yes, writer); | 488 | try link.printExpr(tree, data.yes, writer); |
| 487 | try writer.writeAll(" : "); | 489 | try writer.writeAll(" : "); |
| 488 | try link.printExpr(tree, data.no, writer); | 490 | try link.printExpr(tree, data.no, writer); |
| 489 | try writer.writeAll(")"); | 491 | if (wrap) |
| 492 | try writer.writeAll(")"); | ||
| 490 | }, | 493 | }, |
| 491 | 494 | ||
| 492 | .call => { | 495 | .call => { |