authorgravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-05-29 00:59:55-07:00
committergravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-05-29 00:59:55-07:00
logeefe35dc33f3f2354626c64f066bb20d3f595496
tree9e29d50194c8340409e69d32cb16847e9cd77fe3
parent3ff6202073407ee0560d083011736c187448b190
signature Commit is signed but in an unrecognized format.

some more type-checking errors


8 files changed, 319 insertions(+), 60 deletions(-)

CHANGELOG.md+4
......@@ -1,9 +1,13 @@
11## 0.1.3
22
33- feat(sema): coersion: numeric_literal -> float
4- feat(sema): prevent passing non-runtime compatible types like `type` to opaque functions
45- feat(transform): lhs of assignments now validated
56- feat(transform): mark useless discard
7- feat: implement the `comptime` keyword (many comptime ops are not implemented)
68- fix(sema): properly coerce assignments
9- fix(transform): empty block generated in top level code tripped sema assertion
10- fix(link): emit parenthesis to preserve operator precedence
711
812## 0.1.2
913
src/core/InternPool.zig+20
......@@ -336,6 +336,26 @@ pub const Type = enum(u32) {
336336 return ip.indexToKey(ty.value()).type_function;
337337 }
338338
339 pub fn hasRuntimeRepresentation(ty: Type, ip: *const InternPool) bool {
340 return switch (ty) {
341 .bool,
342 .int32,
343 .float,
344 .string,
345 .void,
346 .@"opaque",
347 => true,
348 .numeric_literal,
349 .type,
350 .noreturn,
351 => false,
352 else => switch (ty.tag(ip)) {
353 .simple_type => unreachable, // all simple types are already printed
354 .type_function => true,
355 },
356 };
357 }
358
339359 pub fn displayName(ty: Type, ip: *const InternPool) std.fmt.Formatter(formatDisplayName) {
340360 return .{ .data = .{ .type = ty, .ip = ip } };
341361 }
src/core/JsLink.zig+49-4
......@@ -69,7 +69,7 @@ pub fn linkJsCode(
6969 try link.decls.putNoClobber(comp.gpa, decl_index, @enumFromInt(gop.index));
7070 }
7171
72 try writer.print("// Language Compiler v0.1.2. Alpha quality software.\n", .{});
72 try writer.print("// Language Compiler v0.1.3. Alpha quality software.\n", .{});
7373
7474 // 2. Print every function decl
7575 for (decls[1..]) |decl_index| {
......@@ -197,7 +197,22 @@ fn printBlock(link: *JsLink, tree: *const JsTree, list: JsTree.IndexList, writer
197197 }
198198}
199199
200fn printExpr(link: *JsLink, tree: *const JsTree, node: Node.Index, writer: anytype) @TypeOf(writer).Error!void {
200fn printExpr(
201 link: *JsLink,
202 tree: *const JsTree,
203 node: Node.Index,
204 writer: anytype,
205) @TypeOf(writer).Error!void {
206 return link.printExprLevel(tree, node, .comma, writer);
207}
208
209fn printExprLevel(
210 link: *JsLink,
211 tree: *const JsTree,
212 node: Node.Index,
213 level: Level,
214 writer: anytype,
215) @TypeOf(writer).Error!void {
201216 var t = tracer.trace(@src());
202217 defer t.end();
203218
......@@ -314,11 +329,16 @@ fn printExpr(link: *JsLink, tree: *const JsTree, node: Node.Index, writer: anyty
314329 => |tag| {
315330 const bin: JsTree.Bin = tree.nodeData(tag, node);
316331 const op: Operator = @field(Operator, @tagName(tag));
317 try link.printExpr(tree, bin.lhs, writer);
332 const inner_level = comptime op.level();
333 if (inner_level.needsParens(level))
334 try writer.writeAll("(");
335 try link.printExprLevel(tree, bin.lhs, inner_level, writer);
318336 try writer.writeAll(" ");
319337 try writer.writeAll(comptime op.symbol());
320338 try writer.writeAll(" ");
321 try link.printExpr(tree, bin.rhs, writer);
339 try link.printExprLevel(tree, bin.rhs, inner_level, writer);
340 if (inner_level.needsParens(level))
341 try writer.writeAll(")");
322342 },
323343
324344 inline .negate,
......@@ -411,6 +431,10 @@ const Level = enum {
411431 new,
412432 access_and_call,
413433 member,
434
435 pub fn needsParens(inner_level: Level, outer_level: Level) bool {
436 return @intFromEnum(outer_level) > @intFromEnum(inner_level);
437 }
414438};
415439
416440const Operator = enum {
......@@ -431,6 +455,27 @@ const Operator = enum {
431455 pre_inc,
432456 pre_dec,
433457
458 pub fn level(op: Operator) Level {
459 return switch (op) {
460 .assign,
461 .assign_add,
462 .assign_sub,
463 .assign_mul,
464 .assign_div,
465 => .assignment,
466 .add, .sub => .additive,
467 .mul, .div => .multiplicative,
468 .eq => .equality,
469 .lt, .gt => .relational,
470
471 .pre_inc,
472 .pre_dec,
473 .bool_not,
474 .negate,
475 => .prefix,
476 };
477 }
478
434479 pub fn symbol(op: Operator) []const u8 {
435480 return switch (op) {
436481 .assign => "=",
src/core/Report.zig+62-4
......@@ -44,6 +44,16 @@ pub const Err = union(enum) {
4444
4545 ast: @import("Parse.zig").AstError,
4646
47 user_compile_error: struct {
48 src: LazyNode,
49 msg: String,
50
51 fn build(data: @This(), ctx: *MsgContext) !void {
52 try ctx.msgNode(try data.src.get(ctx), "{s}", .{
53 data.msg.slice(ctx.report),
54 });
55 }
56 },
4757 unknown_identifier: struct {
4858 node: NodeIndex,
4959
......@@ -64,6 +74,17 @@ pub const Err = union(enum) {
6474 );
6575 }
6676 },
77 builtin_mismatch_args: struct {
78 node: NodeIndex,
79 args: u32,
80
81 fn build(data: @This(), ctx: *MsgContext) !void {
82 try ctx.msgNode(data.node, "built-in function expected {d} arguments, recieved {d}", .{
83 data.args,
84 ctx.ast.extraData(ctx.ast.nodeData(data.node).call.extra, Ast.Call).args.len,
85 });
86 }
87 },
6788 duplicate_function: struct {
6889 node: NodeIndex,
6990 prev: NodeIndex,
......@@ -169,6 +190,28 @@ pub const Err = union(enum) {
169190 });
170191 }
171192 },
193 runtime_incompatible_type: struct {
194 src: LazyNode,
195 type: Type,
196
197 fn build(data: @This(), ctx: *MsgContext) !void {
198 const node = try data.src.get(ctx);
199 try ctx.msgNode(node, "type \"{}\" cannot be represented as a runtime value", .{
200 data.type.displayName(&ctx.comp.intern_pool),
201 });
202 }
203 },
204 param_comptime_only: struct {
205 src: LazyNode,
206 type: Type,
207
208 fn build(data: @This(), ctx: *MsgContext) !void {
209 const node = try data.src.get(ctx);
210 try ctx.msgNode(node, "parameter of comptime type \"{s}\" must be marked \"comptime\"", .{
211 data.type.displayName(&ctx.comp.intern_pool),
212 });
213 }
214 },
172215 assign_builtin: struct {
173216 node: NodeIndex,
174217
......@@ -279,10 +322,10 @@ pub const Warn = union(enum) {
279322 }
280323 },
281324 numeric_int_var_ambiguity: struct {
282 node: LazyNode,
325 src: LazyNode,
283326
284327 fn build(data: @This(), ctx: *MsgContext) !void {
285 const node = try data.node.get(ctx);
328 const node = try data.src.get(ctx);
286329 try ctx.msgNode(node, "constant integer in a \"var\" has an unknown type, assuming \"float\"", .{});
287330 try ctx.msgAlone("add \": float\" or \": int\" after the variable name to set the type", .{});
288331 }
......@@ -356,7 +399,7 @@ pub const String = struct {
356399 start: u32,
357400 len: u32,
358401
359 pub fn slice(str: String, report: Report) []const u8 {
402 pub fn slice(str: String, report: *const Report) []const u8 {
360403 return report.string_bytes[str.start..][0..str.len];
361404 }
362405};
......@@ -392,7 +435,7 @@ pub fn dumpToWriter(report: Report, comp: *Comp, writer: anytype, tty: std.io.tt
392435}
393436
394437pub fn formatItem(
395 report: Report,
438 report: *const Report,
396439 item: Item,
397440 item_type: Item.Type,
398441 comp: *Comp,
......@@ -414,6 +457,7 @@ pub fn formatItem(
414457 .comp = comp,
415458 .file = file,
416459 .ast = &ast_data,
460 .report = report,
417461 .utf8_source = (try file.getSource(comp.gpa)).code,
418462 .components = ArrayListUnmanaged(Component).initCapacity(alloc, 16) catch
419463 unreachable, // stack-fallback allocator has enough space.
......@@ -450,7 +494,11 @@ pub fn formatItem(
450494 try tty.setColor(writer, .dim);
451495 try writer.writeAll(": ");
452496 try tty.setColor(writer, .reset);
497 if (i == 1)
498 try tty.setColor(writer, .bold);
453499 try writer.writeAll(component.message_utf8);
500 if (i == 1)
501 try tty.setColor(writer, .reset);
454502 try writer.writeAll("\n");
455503
456504 const utf8_source = ctx.utf8_source;
......@@ -685,6 +733,7 @@ pub const MsgContext = struct {
685733 file: *File,
686734 comp: *Comp,
687735 ast: *const Ast,
736 report: *const Report,
688737 utf8_source: [:0]const u8,
689738
690739 /// This arena is is a stack fallback with 16kb, then
......@@ -799,6 +848,14 @@ pub const LazyNode = struct {
799848 else => unreachable,
800849 }
801850 },
851 .declaration => {
852 assert(node_tag == .fn_decl);
853 const fn_decl = ctx.ast.extraData(data.fn_decl.extra, Ast.FnDecl);
854 return switch (lazy.operand) {
855 .return_type => fn_decl.return_type.unwrap().?,
856 else => fn_decl.params.slice(ctx.ast)[lazy.operand.getArgId()],
857 };
858 },
802859 else => {},
803860 }
804861
......@@ -823,6 +880,7 @@ pub const Operand = enum(u32) {
823880 lhs = 4,
824881 /// Right hand side of a binary instruction
825882 rhs = 5,
883 _,
826884
827885 /// One of:
828886 /// - An argument of a .call node
src/core/Sema.zig+94-26
......@@ -144,10 +144,15 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value {
144144 defer s.gpa.free(s.params);
145145
146146 for (fn_decl.params.slice(&s.code), 0..) |param, i| {
147 s.params[i] = try s.resolveType(
148 s.inst_map.start.op(Report.Operand.argument(@intCast(i))),
149 param,
150 );
147 const src = s.inst_map.start.op(Report.Operand.argument(@intCast(i)));
148 const ty = try s.resolveType(src, param);
149 if (!ty.hasRuntimeRepresentation(s.ip)) {
150 return s.fail(.param_comptime_only, .{
151 .src = s.lazyNode(src),
152 .type = ty,
153 });
154 }
155 s.params[i] = ty;
151156 }
152157
153158 const return_type = try s.resolveType(
......@@ -398,9 +403,11 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
398403 scratch[i] = coerced;
399404 }
400405 } else {
401 // just copy the args in without checking
402406 for (call.args.slice(&s.code), 0..) |arg, i| {
403 const ref = try s.resolveRefRuntime(arg);
407 const ref = try s.ensureValueRuntime(
408 in.op(Report.Operand.argument(@intCast(i))),
409 try s.resolveRefRuntime(arg),
410 );
404411 scratch[i] = ref;
405412 }
406413 }
......@@ -475,23 +482,15 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
475482 .memory => {
476483 const data = s.code.instData(.memory, in);
477484
478 var initial_value = try s.resolveRefRuntime(data.operand);
485 const initial_value = try s.resolveRefRuntime(data.operand);
479486
480 switch (initial_value.decode()) {
481 .value => |v| switch (v.toKey(s.ip)) {
482 .comptime_float => |val| {
483 try s.report.warn(.numeric_int_var_ambiguity, .{
484 .node = s.nodeLoc(in, .operand),
485 });
486 initial_value = (try s.makeValue(.{ .float = val })).toAir();
487 },
488 else => {},
489 },
490 else => {},
491 }
487 const runtime_known = try s.ensureValueRuntime(
488 in.op(.operand),
489 initial_value,
490 );
492491
493492 return try s.addInstRef(.memory, .{
494 .init = initial_value,
493 .init = runtime_known,
495494 .ty = s.typeOfRef(initial_value),
496495 });
497496 },
......@@ -518,6 +517,29 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
518517 const operand = try s.resolveRefRuntime(as.operand);
519518 return try s.coerceRef(in.op(.rhs), operand, coerce_to.toType(s.ip).?);
520519 },
520 .@"comptime" => {
521 const node: Teg.Inst.RefNode = s.code.instData(.@"comptime", in);
522 const value = try s.resolveRefComptimeAny(node.operand);
523 return value.toAir();
524 },
525 .type_of => {
526 const node: Teg.Inst.RefNode = s.code.instData(.type_of, in);
527 const value = try s.resolveRefComptimeAny(node.operand);
528 return value.typeOf(s.ip).value().toAir();
529 },
530 .compile_error => {
531 const node: Teg.Inst.RefNode = s.code.instData(.compile_error, in);
532 const value = try s.resolveRefComptimeType(
533 in.op(Report.Operand.argument(0)),
534 node.operand,
535 .string,
536 );
537 const str = value.asString(s.ip).toSlice(s.ip);
538 return s.fail(.user_compile_error, .{
539 .src = s.lazyNode(in.op(.main_node)),
540 .msg = try s.report.addString(str),
541 });
542 },
521543 else => |tag| std.debug.panic("TODO: analyze of .{s}", .{@tagName(tag)}),
522544 }
523545}
......@@ -662,6 +684,7 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value {
662684 }
663685 switch (to) {
664686 .@"opaque" => {
687
665688 //
666689 },
667690 .bool => {
......@@ -699,23 +722,68 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value {
699722 else => {},
700723 },
701724 .string => {
702 //
725 // TODO:
703726 },
704727 .noreturn => {
705 //
728 // nothing coerces to `noreturn`
706729 },
707 .type => {
708 if (value == .void) return .void;
730 .numeric_literal => {
731 if (value.toFloat(s.ip)) |float| {
732 return try s.makeValue(.{
733 .comptime_float = float,
734 });
735 }
709736 },
710 .numeric_literal => {},
711 .void => {
737 .type => {
712738 if (value == .void) return .void;
739
740 // non-types do not coerce to types
713741 },
742 .void => if (value == .void) return .void,
714743 else => {},
715744 }
716745 return s.coersionError(src, from, to);
717746}
718747
748fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) !Air.Inst.Ref {
749 switch (ref.decode()) {
750 .value => |v| {
751 const ty = v.typeOf(s.ip);
752 switch (ty) {
753 .numeric_literal => {
754 try s.report.warn(.numeric_int_var_ambiguity, .{
755 .src = s.lazyNode(src),
756 });
757 return (try s.makeValue(.{
758 .float = v.toFloat(s.ip) orelse unreachable,
759 })).toAir();
760 },
761 .type,
762 .noreturn,
763 => return s.fail(.runtime_incompatible_type, .{
764 .src = s.lazyNode(src),
765 .type = ty,
766 }),
767 .@"opaque" => @panic("TODO: represent comptime-known opaques better"),
768 // Allowed at runtime
769 .bool,
770 .float,
771 .int32,
772 .string,
773 .void,
774 => {},
775 _ => switch (ty.tag(s.ip)) {
776 .simple_type => unreachable, // handled above
777 // Allowed at runtime
778 .type_function => {},
779 },
780 }
781 },
782 else => {},
783 }
784 return ref;
785}
786
719787fn resolveDecl(s: *Sema, decl_index: Decl.Index) Error!Value {
720788 const decl = s.ip.declPtr(decl_index);
721789 switch (decl.analysis) {
src/core/Teg.Dump.zig+1
......@@ -171,6 +171,7 @@ const State = struct {
171171 .import,
172172 .fetch,
173173 .compile_error,
174 .type_of,
174175 => |comptime_tag| {
175176 const data = teg.instData(comptime_tag, inst);
176177 try writer.writeAll("(");
src/core/Teg.zig+8-6
......@@ -78,6 +78,8 @@ pub const Inst = struct {
7878 fetch,
7979 /// Implements the `@compileError` builtin
8080 compile_error,
81 /// Implements the `@typeOf` builtin
82 type_of,
8183
8284 pub fn Data(comptime tag: Tag) type {
8385 return switch (tag) {
......@@ -93,7 +95,6 @@ pub const Inst = struct {
9395 .load_decl => StrNode,
9496 .param => DataNode,
9597 .store => PlNode(Store),
96 .@"comptime" => RefNode,
9798
9899 // Single instruction operand
99100 .memory,
......@@ -107,6 +108,8 @@ pub const Inst = struct {
107108 .import,
108109 .fetch,
109110 .compile_error,
111 .@"comptime",
112 .type_of,
110113 => RefNode,
111114
112115 // Two instruction operands
......@@ -403,11 +406,10 @@ pub fn instPayload(teg: *const Teg, comptime tag: Inst.Tag, index: Inst.Index) t
403406}
404407
405408pub fn instNodeOffset(teg: *const Teg, index: Inst.Index) i32 {
406 switch (teg.instTag(index)) {
407 inline else => |tag| {
408 return teg.instData(tag, index).src_node;
409 },
410 }
409 return switch (teg.instTag(index)) {
410 .declaration => 0,
411 inline else => |tag| teg.instData(tag, index).src_node,
412 };
411413}
412414
413415pub fn blockInstructions(teg: *const Teg, index: Inst.Index) []const Inst.Index {
src/core/Transform.zig+81-20
......@@ -276,12 +276,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I
276276 };
277277 try t.extra.list.appendSlice(t.gpa, @ptrCast(temp));
278278
279 const block = try t.addRootInst(.{
280 .tag = .block,
281 .data = undefined,
282 });
283
284 {
279 const top_level_code_ref: Inst.Ref = ref: {
285280 const scratch_top = t.scratch.items.len;
286281 defer t.scratch.shrinkRetainingCapacity(scratch_top);
287282
......@@ -289,11 +284,23 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I
289284 try t.transformModuleStatement(decl, statement);
290285 }
291286
287 // if there are no new instructions, we dont have to create a block context
288 if (scratch_top == t.scratch.items.len) {
289 break :ref .void;
290 }
291
292 const block = try t.addRootInst(.{
293 .tag = .block,
294 .data = undefined,
295 });
296
292297 t.getInstData(block).* = EncodedData.encode(.block, .{
293298 .src_node = decl.nodeOffset(module_node),
294299 .extra_index = try t.extra.append(t.gpa, IndexList, try t.scratchToIndexList(scratch_top)),
295300 });
296 }
301
302 break :ref block.toRef();
303 };
297304
298305 const decl_extra, var trail = try t.extra.appendTrailing(t.gpa, Inst.Decl, .{
299306 .kind = .function,
......@@ -305,7 +312,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I
305312 trail.writeStruct(Inst.DeclFn, .{
306313 .params = .{ .start = 0, .len = 0 },
307314 .return_type = Inst.Ref.void,
308 .body = block.toRef(),
315 .body = top_level_code_ref,
309316 });
310317
311318 t.getInstData(top_level_fn).* = EncodedData.encode(.declaration, .{
......@@ -480,11 +487,12 @@ fn transformExpressionToBlock(
480487
481488 // if there are no new instructions, we dont have to create a block context
482489 if (scratch_top == t.scratch.items.len) {
483 const result = expr orelse unreachable; // null means we added break or return
490 const result = expr orelse unreachable; // null means we *added* break or return
484491
485492 // TODO: evaluate if `if` blocks would be fine in sema with instruction refs
486493 // with this extra if check here, we filter out non-blocks.
487 if (result.toIndex() == null) {
494 if (result.toValue() != null) {
495 assert(block == null);
488496 return result;
489497 }
490498 }
......@@ -519,6 +527,7 @@ fn transformExpressionToBlock(
519527 }
520528
521529 const content = try t.scratchToIndexList(scratch_top);
530 assert(content.len > 0);
522531
523532 t.getInstData(ret_block).* = EncodedData.encode(.block, .{
524533 .src_node = decl.nodeOffset(node),
......@@ -554,25 +563,29 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc
554563 return value;
555564}
556565
557const Builtin = enum {
566pub const Builtin = enum {
558567 js,
559568 import,
560569 fetch,
561570 compile_error,
571 type_of,
562572
563573 const map = std.ComptimeStringMap(Builtin, .{
564 .{ "js", .js },
565 .{ "import", .import },
566 .{ "fetch", .fetch },
574 // sort-lines: start
567575 .{ "compileError", .compile_error },
576 .{ "fetch", .fetch },
577 .{ "import", .import },
578 .{ "js", .js },
579 .{ "typeOf", .type_of },
580 // sort-lines: end
568581 });
569582
570 fn builtin_js(t: *Transform, decl: BlockContext, args: []const NodeIndex) !?Inst.Ref {
583 fn builtin_js(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref {
571584 const trace = tracer.trace(@src());
572585 defer trace.end();
573586
574587 if (args.len != 1) {
575 // try t.report.err(.invalid_builtin_args, .{ .id = "js" });
588 try t.report.err(.builtin_mismatch_args, .{ .node = node, .args = 1 });
576589 return .void;
577590 }
578591
......@@ -586,6 +599,48 @@ const Builtin = enum {
586599 }),
587600 });
588601 }
602
603 fn builtin_compileError(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref {
604 const trace = tracer.trace(@src());
605 defer trace.end();
606
607 if (args.len != 1) {
608 try t.report.err(.builtin_mismatch_args, .{ .node = node, .args = 1 });
609 return .void;
610 }
611
612 const source = try t.transformExpression(decl, args[0]) orelse
613 return null;
614
615 return try t.addInstRef(.{
616 .tag = .compile_error,
617 .data = EncodedData.encode(.js, .{
618 .src_node = decl.nodeOffset(node),
619 .operand = source,
620 }),
621 });
622 }
623
624 fn builtin_typeOf(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref {
625 const trace = tracer.trace(@src());
626 defer trace.end();
627
628 if (args.len != 1) {
629 try t.report.err(.builtin_mismatch_args, .{ .node = node, .args = 1 });
630 return .void;
631 }
632
633 const source = try t.transformExpression(decl, args[0]) orelse
634 return null;
635
636 return try t.addInstRef(.{
637 .tag = .type_of,
638 .data = EncodedData.encode(.js, .{
639 .src_node = decl.nodeOffset(node),
640 .operand = source,
641 }),
642 });
643 }
589644};
590645
591646/// This is the primary transformation function: converting an AST expression into instructions.
......@@ -874,10 +929,11 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
874929 const args = base.slice(&t.ast);
875930
876931 return switch (builtin) {
877 .js => Builtin.builtin_js(t, decl, args),
878 // .import => Builtin.import(t, decl, args),
879 // .fetch => Builtin.fetch(t, decl, args),
880 // .compile_error => Builtin.compile_error(t, decl, args),
932 .js => Builtin.builtin_js(t, decl, node, args),
933 // .import => Builtin.import(t, decl, node, args),
934 // .fetch => Builtin.fetch(t, decl, node, args),
935 .compile_error => Builtin.builtin_compileError(t, decl, node, args),
936 .type_of => Builtin.builtin_typeOf(t, decl, node, args),
881937 else => @panic("TODO"),
882938 };
883939 },
......@@ -1071,6 +1127,11 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
10711127 return null; // control flow ends
10721128 },
10731129
1130 .group => {
1131 const group_node = t.ast.nodeData(node).un;
1132 return try t.transformExpression(decl, group_node.expr);
1133 },
1134
10741135 else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}),
10751136 }
10761137}