1//! Transform pass converts an Ast into a Hir. See Hir.zig for more details
2//! about the resulting data structure. Transforming handles:
3//!
4//! - Flattening nested expressions into instruction sequences.
5//! - Resolving all local variables into reference or declaration names.
6//! - Resolving breaks and returns to their generated block id.
7//! - Annotating expression statements with `.discard` and `.ensure_void`
8//! - Resolving implicit expression with their corresponding result type
9//!
10//! Some trivial diagnostics such as unused variables, unknown result types,
11//! invalid assignment targets, and other style issues are also reported here.
12
13gpa: Allocator,
14arena: Allocator,
15
16ast: Ast,
17
18// these values are allocated through `gpa`, so that resulting IR can be
19// trivially shrunk before returning.
20
21insts: Inst.List,
22extra: ExtraStore,
23string_bytes: std.ArrayListUnmanaged(u8),
24report: Report.Builder,
25
26// however, these are allocated through `arena`, since they are always freed at
27// the end of the pass.
28
29scratch: core.U32Stack,
30id_decode_buf: std.ArrayList(u8),
31scopes: std.ArrayListUnmanaged(Scope),
32capture_scopes: std.ArrayListUnmanaged(CaptureScope),
33
34has_warned_top_level_statement: bool,
35
36pub const Error = error{
37 /// A `Report` will contain at least one error.
38 TransformFail,
39} || Allocator.Error;
40
41pub fn transform(ast: Ast, gpa: Allocator, fail_report: *Report) Error!Hir {
42 const trace = tracer.trace(@src());
43 defer trace.end();
44
45 var sfa = std.heap.stackFallback(4096, gpa);
46
47 var t: Transform = .{
48 .gpa = gpa,
49 .arena = gpa,
50 .ast = ast,
51 .insts = .{},
52 .extra = .empty,
53 .string_bytes = .empty,
54 .report = .init(gpa),
55 .scratch = .empty,
56 .scopes = .empty,
57 .capture_scopes = .empty,
58 .id_decode_buf = std.ArrayList(u8).initCapacity(sfa.get(), 4096) catch
59 unreachable, // stack-fallback has enough space
60 .has_warned_top_level_statement = false,
61 };
62 defer t.deinit();
63
64 try t.string_bytes.append(t.gpa, 0);
65
66 const module = t.transformModule(0) catch |err| switch (err) {
67 error.OutOfMemory => return err,
68 };
69
70 const report = try t.report.finalize();
71 if (report.errors.len > 0) {
72 fail_report.* = report;
73 return error.TransformFail;
74 }
75
76 assert(module == .main_module_inst);
77
78 return .{
79 .insts = t.insts.toOwnedSlice(),
80 .string_bytes = try t.string_bytes.toOwnedSlice(t.gpa),
81 .extra = try t.extra.toOwnedStore(t.gpa),
82 .report = report,
83 };
84}
85
86fn deinit(t: *Transform) void {
87 assert(t.scopes.items.len == 0);
88 t.id_decode_buf.deinit();
89 t.scratch.list.deinit(t.arena);
90 t.scopes.deinit(t.arena);
91 t.capture_scopes.deinit(t.arena);
92 t.string_bytes.deinit(t.gpa);
93 t.insts.deinit(t.gpa);
94 t.extra.deinit(t.gpa);
95 t.* = undefined;
96}
97
98/// Represents how the parent node is going to implicitly cast this expression.
99///
100/// Consider an expression like `let x: S = .{ y: 1 }`. The type from the
101/// declaration must impact the visited struct initialization expression. This
102/// behavior is recursive, so something more complex can properly infer:
103///
104/// struct S { a: int }
105/// struct Y {}
106/// let x: [](S, []Y) = [ .{ .{ a: 1 }, [ .{} ] } ];
107///
108/// The innermost `.{}` must eventually analyze to `Y{}`, which can be unwrapped
109/// from the type expression via specialized instructions for this purpose.
110const ResultType = union(enum) {
111 /// There is no result type available
112 none,
113 /// The result type was consumed
114 used,
115 /// The expression will be cast to this ref.
116 ref: Inst.Ref,
117 /// The expression will be cast to the type of this ref
118 type_of: Inst.Ref,
119 /// The expression is an argument to a function.
120 argument: Hir.ResultTypeArgument,
121 /// The expression is a field in a struct initialization
122 struct_field: Hir.ResultTypeStructField,
123 /// The expression is a field in a tuple initialization
124 tuple_field: Hir.ResultTypeTupleField,
125 /// The expression is a field in an array initialization
126 array_value: Inst.Ref,
127
128 // /// The expression is unknown field
129 // field: Hir.ResultTypeField,
130
131 /// This will return an instruction pointing to the the type
132 pub fn consume(result: *ResultType, t: *Transform) !?Inst.Ref {
133 if (result.* == .none) return null;
134 defer result.* = .used;
135 return switch (result.*) {
136 .none => unreachable,
137 .used => unreachable, // Use after free
138 .ref => |r| r,
139 .type_of => |r| try t.addRef(.type_of, .{
140 // Result types do not have associated nodes which is okay as `type_of` cannot
141 // fail in the context it is used in. A predictable poison value is used instead
142 // so that an attempt to print the following will cause a crash.
143 .src_node = std.math.maxInt(i32),
144 .operand = r,
145 }),
146 .argument => |r| try t.addRef(.result_type_argument, r),
147 .struct_field => |r| try t.addRef(.result_type_struct_field, r),
148 .tuple_field => |r| try t.addRef(.result_type_tuple_field, r),
149 .array_value => |r| try t.addRef(.result_type_array_value, .{ .type = r }),
150 };
151 }
152};
153
154const BlockContext = struct {
155 decl_node_offset: i32,
156 break_target: ?*BreakTarget = null,
157 return_target: ?*const ReturnTarget = null,
158
159 /// This struct is always stored on the stack; the idea is to pass mutable
160 /// pointers to it. When `break :brk` is encountered, it will generate the
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.
163 const BreakTarget = struct {
164 label: ?[]const u8,
165 /// This allows `continue` as well as implicit break targeting.
166 is_loop: bool,
167 /// If `null`, this label was unused.
168 block_index: ?Inst.Index = null,
169 parent: ?*BreakTarget = null,
170 result_type: *ResultType,
171
172 fn getInst(target: *BreakTarget, t: *Transform) !Inst.Index {
173 return target.block_index orelse {
174 const inst = try t.addUntracked(.block, undefined);
175 target.block_index = inst;
176 return inst;
177 };
178 }
179 };
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
189 fn init(root_node: NodeIndex) BlockContext {
190 return .{
191 .decl_node_offset = @intCast(root_node),
192 };
193 }
194
195 fn nodeOffset(self: BlockContext, node: NodeIndex) i32 {
196 return @as(i32, @intCast(node)) - self.decl_node_offset;
197 }
198
199 fn declOffset(self: BlockContext, node: NodeIndex) BlockContext {
200 return .{
201 .decl_node_offset = self.nodeOffset(node),
202 };
203 }
204
205 fn withReturn(self: BlockContext, return_target: ?*const ReturnTarget) BlockContext {
206 return .{
207 .decl_node_offset = self.decl_node_offset,
208 .return_target = return_target,
209 .break_target = null,
210 };
211 }
212
213 fn withBreak(self: BlockContext, break_target: ?*BreakTarget) BlockContext {
214 return .{
215 .decl_node_offset = self.decl_node_offset,
216 .return_target = self.return_target,
217 .break_target = break_target,
218 };
219 }
220
221 fn lookupNamedBreak(self: BlockContext, label: []const u8) ?*BreakTarget {
222 var target = self.break_target orelse return null;
223 while (true) {
224 if (target.label) |target_label| {
225 if (mem.eql(u8, label, target_label)) {
226 return target;
227 }
228 }
229
230 target = target.parent orelse return null;
231 }
232 }
233};
234
235// TODO: optimize this structure
236//
237// If Transform does not need to track more than a symbol list for scope
238// (BlockContext kind of does this, so it's likely), this can be refactored to a
239// custom structure which is a single array of { Hash, Key, Symbol }, where each
240// block context stores the index into this array where it's map starts. Lookups
241// are done by scanning the map backwards for a hash, which would allow for
242// trivial memory re-use.
243const Scope = struct {
244 symbols: std.StringArrayHashMapUnmanaged(Symbol) = .empty,
245
246 fn addWarnings(s: Scope, t: *Transform) !void {
247 for (s.symbols.values()) |value| {
248 try value.addWarnings(t);
249 }
250 }
251
252 fn deinit(s: *Scope, t: *Transform) void {
253 for (s.symbols.keys()) |k| {
254 if (!isSliceInBuffer(u8, t.ast.utf8_source, k)) {
255 t.gpa.free(k);
256 }
257 }
258 s.symbols.deinit(t.gpa);
259 s.* = undefined;
260 }
261};
262
263/// Sema requires that function expressions must know their up-front captures,
264/// so it can classify a function as comptime or runtime.
265const CaptureScope = struct {
266 start_index: Hir.Inst.Index,
267 symbol_uses: std.AutoArrayHashMapUnmanaged(Hir.Inst.Index, void) = .{},
268
269 fn deinit(s: *CaptureScope, t: *Transform) void {
270 s.symbol_uses.deinit(t.gpa);
271 s.* = undefined;
272 }
273};
274
275/// Identifier nodes reference a Symbol entry through the Transform's symbol
276/// table. The most important part of this is being able to convert it into the
277/// corresponding Ref or load_decl instruction when an identifier is visited.
278/// Additionally, there is bookkeeping information for warnings like "variable
279/// not mutated", which is reported just before a scope is deleted.
280const Symbol = struct {
281 kind: Kind,
282 flags: Flags,
283 src_node: NodeIndex,
284 value: union {
285 mutable: Inst.Index,
286 immutable: Inst.Ref,
287 param_index: u32,
288 none: void,
289 },
290 result_type_ref: Inst.Ref,
291 discarded: OptionalNodeIndex,
292
293 const Flags = packed struct {
294 result_type_kind: enum(u1) { present, missing },
295 used: bool,
296 mutated: bool,
297 };
298
299 const Kind = enum(u8) {
300 /// Named declaration like `let`, `fn`, `struct`, `module`
301 declaration,
302 /// Turns into a .variable_declaration
303 variable_declaration,
304 /// Function parameter
305 param,
306 /// Mutable local variable
307 mutable,
308 /// Immutable local variable
309 immutable,
310 };
311
312 fn init(kind: Kind) Symbol {
313 return .{ .kind = kind };
314 }
315
316 fn resultType(symbol: *const Symbol) ResultType {
317 return switch (symbol.flags.result_type_kind) {
318 .present => .{ .ref = symbol.result_type_ref },
319 .missing => .{ .type_of = symbol.result_type_ref },
320 };
321 }
322
323 const SymbolResultType = union(enum) {
324 /// Points to the resolved result type (.ref), as is.
325 present: Inst.Ref,
326 /// This points to the value. Which means a `type_of` instruction
327 /// must be inserted before it may be used.
328 missing: Inst.Ref,
329
330 pub fn resolve(srt: SymbolResultType) ResultType {
331 return switch (srt) {
332 .present => |type_ref| .{ .ref = type_ref },
333 .missing => |value_ref| .{ .type_of = value_ref },
334 };
335 }
336 };
337
338 pub fn addWarnings(symbol: Symbol, t: *Transform) !void {
339 if (!symbol.flags.mutated and (symbol.kind == .mutable or symbol.kind == .variable_declaration)) {
340 try t.report.warn(.not_mutated_var, .{
341 .node = symbol.src_node,
342 });
343 }
344
345 if (!symbol.flags.used) {
346 try t.report.warn(.unused, .{
347 .node = symbol.src_node,
348 .kind = switch (symbol.kind) {
349 .declaration => .declaration,
350 .param => .param,
351 .mutable, .variable_declaration => .variable,
352 .immutable => switch (t.ast.nodeTag(symbol.src_node)) {
353 .param_decl => .param,
354 else => .constant,
355 },
356 },
357 });
358 } else if (symbol.discarded.unwrap()) |discard| {
359 try t.report.warn(.unnecessary_discard, .{
360 .node = discard,
361 });
362 }
363 }
364};
365
366const BuiltIn = struct {
367 type: Inst.Ref,
368 value: Inst.Ref,
369};
370
371const built_in_ids = std.StaticStringMap(BuiltIn).initComptime(.{
372 .{ "void", BuiltIn{ .value = .void, .type = .void } },
373 .{ "true", BuiltIn{ .value = .bool_true, .type = .bool_type } },
374 .{ "false", BuiltIn{ .value = .bool_false, .type = .bool_type } },
375 .{ "int", BuiltIn{ .value = .int32_type, .type = .type_type } },
376 .{ "float", BuiltIn{ .value = .float_type, .type = .type_type } },
377 .{ "string", BuiltIn{ .value = .string_type, .type = .type_type } },
378 .{ "opaque", BuiltIn{ .value = .opaque_type, .type = .type_type } },
379 // .{ "bigint", BuiltIn{ .value = .bigint_type, .type = .type_type } },
380 .{ "bool", BuiltIn{ .value = .bool_type, .type = .type_type } },
381 .{ "never", BuiltIn{ .value = .never_type, .type = .type_type } },
382 .{ "type", BuiltIn{ .value = .type_type, .type = .type_type } },
383 // .{ "std", BuiltIn{ .value = .standard_library, .type = ... } },
384});
385
386fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.Index {
387 const trace = tracer.trace(@src());
388 defer trace.end();
389
390 const module = try t.addUntracked(.module, undefined);
391
392 const decl: BlockContext = .init(module_node);
393
394 try t.pushScope();
395 defer t.popScope();
396
397 // Pass 1 is a light visit over every statement in order to extract
398 // declaration names. This "hoists" the names so that declarations can be
399 // referenced before their definition appears in code.
400 var previsit_state: PrevisitModuleState = .{ .test_decls = .empty };
401 defer previsit_state.test_decls.deinit(t.gpa);
402 const statements = t.ast.nodeStatements(module_node);
403 for (statements) |statement| {
404 try t.previsitModuleStatement(&previsit_state, statement);
405 }
406
407 // Pass 2 processes all declarations in order, recursively
408 // visiting every Ast node.
409 const s = t.scope();
410 const decl_len = s.symbols.count() + previsit_state.test_decls.items.len;
411 const decls = try t.gpa.alloc(Inst.Index, decl_len);
412 defer t.gpa.free(decls);
413
414 {
415 s.symbols.lockPointers();
416 defer s.symbols.unlockPointers();
417
418 for (s.symbols.values(), 0..) |value, i| {
419 decls[i] = try t.transformDecl(decl, value.src_node);
420 }
421 }
422
423 for (previsit_state.test_decls.items, s.symbols.count()..) |test_decl_node, i| {
424 decls[i] = try t.transformTestDecl(decl, test_decl_node);
425 }
426
427 const declarations: IndexList = .{
428 .start = @intCast(t.extra.list.items.len),
429 .len = @intCast(decls.len),
430 };
431 try t.extra.list.appendSlice(t.gpa, @ptrCast(decls));
432
433 t.getInstData(module).* = .encode(.module, .{
434 .src_node = 0,
435 .extra_index = try t.extra.append(t.gpa, IndexList, declarations),
436 });
437
438 // `fn main` is a special function as it does not have to be public
439 // in order for the compiler to use it as the entry point.
440 if (module_node == 0) {
441 if (t.scope().symbols.getPtr("main")) |main| {
442 main.flags.used = true;
443 }
444 }
445
446 try t.scope().addWarnings(t);
447
448 return module;
449}
450
451const PrevisitModuleState = struct {
452 test_decls: std.ArrayListUnmanaged(NodeIndex),
453};
454
455fn previsitModuleStatement(t: *Transform, state: *PrevisitModuleState, node: NodeIndex) Allocator.Error!void {
456 const trace = tracer.trace(@src());
457 defer trace.end();
458
459 switch (t.ast.nodes.items(.tag)[node]) {
460 // The paths of these three are all very similar and
461 // it is worth grouping them together
462 inline .fn_decl,
463 .struct_decl,
464 .var_decl,
465 => |tag| {
466 const data = switch (tag) {
467 .var_decl => t.ast.fullVarDecl(node),
468 .fn_decl => t.ast.fullFnDecl(node),
469 .struct_decl => t.ast.fullStructDecl(node),
470 else => @compileError(unreachable),
471 };
472
473 const new_kind: Symbol.Kind = if (tag == .var_decl and !data.is_const)
474 .variable_declaration
475 else
476 .declaration;
477
478 var id, const id_allocated = try t.decodeIdentifierExtra(data.id);
479 if (id_allocated)
480 id = try t.gpa.dupe(u8, id);
481
482 const gop = try t.scope().symbols.getOrPut(t.gpa, id);
483 if (gop.found_existing) {
484 if (id_allocated) t.gpa.free(id);
485
486 try t.report.err(.duplicate_function, .{
487 .node = node,
488 .prev = gop.value_ptr.src_node,
489 });
490 return;
491 }
492
493 gop.value_ptr.* = .{
494 .kind = new_kind,
495 .flags = .{
496 .result_type_kind = undefined,
497 .used = false,
498 .mutated = false,
499 },
500 .src_node = node,
501 .discarded = .none,
502 .result_type_ref = undefined,
503 .value = undefined,
504 };
505 },
506 .test_decl => {
507 try state.test_decls.append(t.arena, node);
508 },
509 else => if (!t.has_warned_top_level_statement) {
510 t.has_warned_top_level_statement = true;
511 try t.report.err(.top_level_statement, .{ .src = node });
512
513 // Visit the statement so that any identifiers get tracked, so that mistakes in the code get caught
514 const insts_top = t.insts.len;
515 defer t.insts.len = insts_top;
516 const extra_top = t.extra.list.items.len;
517 defer t.extra.list.items.len = extra_top;
518 const string_top = t.string_bytes.items.len;
519 defer t.string_bytes.items.len = string_top;
520
521 _ = transformBlockStatement(t, .init(node), node, null) catch |err| switch (err) {
522 error.OutOfMemory => return err,
523 };
524 },
525 }
526}
527
528fn transformDecl(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
529 const trace = tracer.trace(@src());
530 defer trace.end();
531
532 const decl_index = try t.addUntracked(.declaration, undefined);
533
534 const inner_ctx = outer_decl.declOffset(node);
535 const body = try t.transformToBlock(inner_ctx, node, .untracked_result, .none, .run_time);
536
537 const inst_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_index);
538
539 const id_encoded = t.ast.tokenSlice(t.ast.idToken(node));
540 const id = try t.decodeIdentifier(id_encoded);
541 const name = try t.addNullTerminatedString(id);
542
543 var hasher: std.Build.Cache.HashHelper = .{ .hasher = core.Cache.hasher_init };
544 hasher.addBytes(id);
545
546 const temp_teg: Hir = .{
547 .insts = t.insts.slice(),
548 .extra = t.extra.constant(),
549 .string_bytes = t.string_bytes.items,
550 .report = undefined,
551 };
552
553 const tag_list = temp_teg.insts.items(.tag)[@intFromEnum(decl_index) + 1 ..];
554 const data_list = temp_teg.insts.items(.data)[@intFromEnum(decl_index) + 1 ..];
555
556 temp_teg.writeToHasher(&hasher, tag_list, data_list, decl_index);
557
558 const decl_extra = try t.extra.append(t.gpa, Hir.Decl, .{
559 .name = name,
560 .hash = hasher.hasher.finalResult(),
561 .instruction_count = inst_count,
562 .body = body,
563 });
564
565 t.getInstData(decl_index).* = .encode(.declaration, .{
566 .src_node = outer_decl.nodeOffset(node),
567 .extra_index = decl_extra,
568 });
569
570 return decl_index;
571}
572
573fn transformTestDecl(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
574 const trace = tracer.trace(@src());
575 defer trace.end();
576
577 const decl_index = try t.addUntracked(.test_declaration, undefined);
578
579 const test_decl = t.ast.nodeData(node).test_decl;
580 if (test_decl.doc_test_name != .none) @panic("TODO");
581 const body_node = test_decl.block;
582
583 const inner_ctx = outer_decl.declOffset(node);
584 const body = try t.transformToBlock(inner_ctx, body_node, .untracked_result, .none, .run_time);
585
586 const inst_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_index);
587
588 var hasher: std.Build.Cache.HashHelper = .{ .hasher = core.Cache.hasher_init };
589
590 const temp_teg: Hir = .{
591 .insts = t.insts.slice(),
592 .extra = t.extra.constant(),
593 .string_bytes = t.string_bytes.items,
594 .report = undefined,
595 };
596
597 const tag_list = temp_teg.insts.items(.tag)[@intFromEnum(decl_index) + 1 ..];
598 const data_list = temp_teg.insts.items(.data)[@intFromEnum(decl_index) + 1 ..];
599
600 temp_teg.writeToHasher(&hasher, tag_list, data_list, decl_index);
601
602 const decl_extra = try t.extra.append(t.gpa, Hir.Decl, .{
603 .name = .empty,
604 .hash = hasher.hasher.finalResult(),
605 .instruction_count = inst_count,
606 .body = body,
607 });
608
609 t.getInstData(decl_index).* = .encode(.test_declaration, .{
610 .src_node = outer_decl.nodeOffset(node),
611 .extra_index = decl_extra,
612 });
613
614 return decl_index;
615}
616
617const TransformToBlockMode = enum {
618 tracked_result,
619 untracked_result,
620 function_body,
621
622 fn ResultType(mode: TransformToBlockMode) type {
623 return switch (mode) {
624 .tracked_result => *Transform.ResultType,
625 else => Transform.ResultType,
626 };
627 }
628
629 fn InstType(mode: TransformToBlockMode) type {
630 return switch (mode) {
631 .function_body => Inst.Index,
632 else => Inst.Ref,
633 };
634 }
635};
636
637const BlockType = enum {
638 run_time,
639 compile_time,
640};
641
642/// Visit and convert an expression into either a Value with no instructions, or
643/// a block instruction containing the expression. This is important for
644/// function bodies, loop bodies, branches, and certain mode-switching contexts
645/// such as `comptime` and `@typeOf`.
646fn transformToBlock(
647 t: *Transform,
648 ctx: BlockContext,
649 node: NodeIndex,
650 comptime mode: TransformToBlockMode,
651 result_type: mode.ResultType(),
652 block_type: BlockType,
653) Allocator.Error!mode.InstType() {
654 const trace = tracer.trace(@src());
655 defer trace.end();
656
657 const scratch = t.scratch.mark();
658 defer scratch.deinit();
659
660 // The result type is only passed by reference if mode is tracked_result.
661 const copies_result_type = mode.ResultType() == ResultType;
662 var result_type_storage = if (copies_result_type) result_type;
663
664 var ret = if (mode == .function_body)
665 BlockContext.ReturnTarget{
666 .result_type = result_type_storage,
667 .block_index = try t.addUntracked(.block, undefined),
668 };
669
670 // For function blocks, add .param instructions for all of the parameters
671 if (mode == .function_body) {
672 const s = t.scopes.getLast();
673 for (s.symbols.values(), 0..) |*param, i| {
674 assert(param.kind == .param);
675 const ref = try t.addRef(.param, .{
676 .src_node = ctx.nodeOffset(node),
677 .data = @intCast(i),
678 });
679 param.value = .{ .immutable = ref };
680 }
681
682 if (result_type == .ref and result_type.ref.decode() == .index) {
683 result_type_storage = .{
684 .ref = try t.addRef(.param, .{
685 .src_node = ctx.nodeOffset(node),
686 .data = std.math.maxInt(u32),
687 }),
688 };
689 }
690 }
691
692 const expr = try t.transformExpression(
693 // Function blocks are allowed to return, in which
694 // it will return to this block.
695 if (mode == .function_body)
696 ctx.withReturn(&ret)
697 else
698 ctx,
699 // Pass result type pointer
700 if (copies_result_type)
701 &result_type_storage
702 else
703 result_type,
704 node,
705 );
706
707 // if we were given a value without any new instructions, we can just
708 // forward that value.
709 if (mode != .function_body and scratch.len() == 0) {
710 const result = expr orelse {
711 assert(t.report.errors.items.len > 0);
712 return .void; // null means control flow ended, and with no instructions guarantees a compile error
713 };
714
715 if (result.decode() == .value) {
716 return result;
717 }
718 }
719
720 // If we analyzed a block, then just use that. No need for an extra wrapper block.
721 if (expr) |result| {
722 if (result.index()) |index| {
723 if (t.insts.items(.tag)[@intFromEnum(index)] == .block) {
724 if (mode == .function_body)
725 return index;
726 return result;
727 }
728 }
729 }
730
731 // Otherwise try using the function return block, or create a new one
732 const ret_block = if (mode == .function_body)
733 ret.block_index
734 else switch (block_type) {
735 .run_time => try t.addUntracked(.block, undefined),
736 .compile_time => try t.addUntracked(.@"comptime", undefined),
737 };
738
739 // The block must end with a break
740 if (expr) |result| {
741 const result_type_ptr = if (copies_result_type)
742 &result_type_storage
743 else
744 result_type;
745 const coerced_result = switch (result_type_ptr.*) {
746 .none, .used => result,
747 else => try t.addRef(.coercion, .{
748 .src_node = ctx.nodeOffset(node),
749 .extra_index = try t.extra.append(t.gpa, Hir.Coercion, .{
750 .type = try result_type_ptr.consume(t) orelse
751 unreachable, // `.none` already handled
752 .operand = result,
753 }),
754 }),
755 };
756
757 _ = try t.addIndex(.@"break", .{
758 .src_node = ctx.nodeOffset(node),
759 .extra_index = try t.extra.append(t.gpa, Hir.Break, .{
760 .block = ret_block,
761 .operand = coerced_result,
762 }),
763 });
764 }
765
766 const content = try t.scratchToIndexList(scratch);
767 assert(content.len > 0);
768
769 t.getInstData(ret_block).* = .encode(.block, .{
770 .src_node = ctx.nodeOffset(node),
771 .extra_index = try t.extra.append(t.gpa, IndexList, content),
772 });
773
774 return if (mode == .function_body) ret_block else ret_block.toRef();
775}
776
777fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Allocator.Error!Inst.Ref {
778 const trace = tracer.trace(@src());
779 defer trace.end();
780
781 const param_decl = t.ast.fullParamDecl(param);
782 const ty = try t.transformToBlock(
783 decl,
784 param_decl.type orelse
785 @panic("TODO: inferred param types"),
786 .untracked_result,
787 .{ .ref = .type_type },
788 .compile_time,
789 );
790
791 var id, const id_allocated = try t.decodeIdentifierExtra(param_decl.id);
792 if (id_allocated)
793 id = try t.gpa.dupe(u8, id);
794
795 // add `sym` into the scope so that the next param's type
796 // can use this param's value, if known at compile time.
797 const sym = try t.scope().symbols.getOrPut(t.gpa, id);
798 if (sym.found_existing) {
799 if (id_allocated) t.gpa.free(id);
800 try t.report.err(.duplicate_param, .{
801 .node = param,
802 .prev = sym.value_ptr.src_node,
803 });
804 return .void;
805 }
806
807 sym.value_ptr.* = .{
808 .src_node = param,
809 .kind = .param,
810 .flags = .{
811 .result_type_kind = .present,
812 .used = false,
813 .mutated = false,
814 },
815 .value = .{ .none = {} },
816 .result_type_ref = ty,
817 .discarded = .none,
818 };
819
820 return ty;
821}
822
823pub const Builtin = enum {
824 @"extern",
825 js_new,
826 import,
827 fetch,
828 compile_error,
829 type_of,
830
831 const map = std.StaticStringMap(Builtin).initComptime(.{
832 // sort-lines: start
833 .{ "compileError", .compile_error },
834 .{ "fetch", .fetch },
835 .{ "import", .import },
836 .{ "extern", .@"extern" },
837 .{ "jsNew", .js_new },
838 .{ "typeOf", .type_of },
839 // sort-lines: end
840 });
841
842 fn builtin_extern(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref {
843 const trace = tracer.trace(@src());
844 defer trace.end();
845
846 if (args.len != 1) {
847 try t.report.err(.builtin_mismatch_args, .{ .node = node, .args = 1 });
848 return .void;
849 }
850
851 var result: ResultType = .{ .ref = .string_type };
852 const source = try t.transformExpression(decl, &result, args[0]) orelse return null;
853
854 return try t.addRef(.@"extern", .{
855 .src_node = decl.nodeOffset(args[0]),
856 .operand = source,
857 });
858 }
859
860 fn builtin_jsNew(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref {
861 const trace = tracer.trace(@src());
862 defer trace.end();
863
864 if (args.len == 0) {
865 try t.report.err(.builtin_mismatch_args, .{ .node = node, .args = 1 });
866 return .void;
867 }
868
869 var opaque_type: ResultType = .{ .ref = .opaque_type };
870 const base = try t.transformExpression(decl, &opaque_type, args[0]) orelse return null;
871 const args_list = try t.transformExpressionList(decl, .{ .ref = .opaque_type }, args[1..]) orelse return null;
872
873 return try t.addRef(.js_new, .{
874 .src_node = decl.nodeOffset(args[0]),
875 .extra_index = try t.extra.append(t.gpa, Hir.JsNew, .{
876 .base = base,
877 .args = args_list,
878 }),
879 });
880 }
881
882 fn builtin_compileError(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref {
883 const trace = tracer.trace(@src());
884 defer trace.end();
885
886 if (args.len != 1) {
887 try t.report.err(.builtin_mismatch_args, .{ .node = node, .args = 1 });
888 return .void;
889 }
890
891 var string_type: ResultType = .{ .ref = .string_type };
892 const source = try t.transformExpression(decl, &string_type, args[0]) orelse
893 return null;
894
895 return try t.addRef(.compile_error, .{
896 .src_node = decl.nodeOffset(node),
897 .operand = source,
898 });
899 }
900
901 fn builtin_typeOf(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref {
902 const trace = tracer.trace(@src());
903 defer trace.end();
904
905 if (args.len != 1) {
906 try t.report.err(.builtin_mismatch_args, .{ .node = node, .args = 1 });
907 return .void;
908 }
909
910 var result: ResultType = .none;
911 const source = try t.transformExpression(decl, &result, args[0]) orelse
912 return null;
913
914 return try t.addRef(.type_of, .{
915 .src_node = decl.nodeOffset(node),
916 .operand = source,
917 });
918 }
919};
920
921/// This is the primary transformation function: converting an AST expression into instructions.
922/// `null` represents a `return` or `break`, aka this branch of code is dead.
923fn transformExpression(
924 t: *Transform,
925 ctx: BlockContext,
926 result: *ResultType,
927 node: NodeIndex,
928) Allocator.Error!?Inst.Ref {
929 const node_tag = t.ast.nodes.items(.tag)[node];
930
931 const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)});
932 defer trace.end();
933
934 switch (node_tag) {
935 .root,
936 .param_decl,
937 .struct_field,
938 => unreachable, // not an expression
939
940 .id => {
941 const id = try t.decodeIdentifier(t.ast.basicNodeSlice(node));
942 if (id.len == 0 or mem.containsAtLeast(u8, id, 1, &[1]u8{'\x00'})) {
943 try t.report.err(.invalid_string_identifier, .{ .node = node });
944 return .void;
945 }
946
947 if (built_in_ids.get(id)) |built_in| {
948 // Elide the coercion when the type is known to be the type of the built-in
949 if (result.* == .ref and result.ref == built_in.type) {
950 result.* = .used;
951 }
952
953 return built_in.value;
954 }
955
956 const lookup = t.lookupId(id) orelse {
957 try t.report.err(.unknown_identifier, .{ .node = node });
958 return .void;
959 };
960
961 lookup.flags.used = true;
962
963 switch (lookup.kind) {
964 .declaration,
965 .variable_declaration,
966 => return try t.addRef(.load_decl, .{
967 .src_node = ctx.nodeOffset(node),
968 .string = try t.addNullTerminatedString(id),
969 }),
970 .mutable => {
971 const ref = lookup.value.mutable.toRef();
972 try t.markCapturedRef(ref);
973 return ref;
974 },
975 .param, .immutable => {
976 const ref = lookup.value.immutable;
977 try t.markCapturedRef(ref);
978 return ref;
979 },
980 }
981 },
982
983 // var_decl *expression* is special cased for decl contents.
984 // instead of defining a variable, it will just perform the coercion.
985 .var_decl => {
986 const decl = t.ast.fullVarDecl(node);
987 var type_result: ResultType = .{ .ref = .type_type };
988 const type_ref = if (decl.type) |ty|
989 try t.transformExpression(ctx, &type_result, ty) orelse
990 return null
991 else
992 null;
993
994 var value_result: ResultType = if (type_ref) |ty| .{ .ref = ty } else .none;
995 const data_ref = try t.transformExpression(ctx, &value_result, decl.init) orelse
996 return null;
997
998 if (value_result == .used or type_ref == null)
999 return data_ref;
1000
1001 return try t.addRef(.coercion, .{
1002 .src_node = ctx.nodeOffset(node),
1003 .extra_index = try t.extra.append(t.gpa, Hir.Coercion, .{
1004 .type = type_ref.?,
1005 .operand = data_ref,
1006 }),
1007 });
1008 },
1009
1010 inline .number_int, .number_float => |tag| {
1011 const text = t.ast.basicNodeSlice(node);
1012 const value = switch (tag) {
1013 .number_int => std.fmt.parseFloat(f64, text) catch @panic("TODO: write my own float parser"),
1014 .number_float => std.fmt.parseFloat(f64, text) catch @panic("TODO: write my own float parser"),
1015 else => @compileError(unreachable),
1016 };
1017
1018 const has_zero_prefix = value != 0 and
1019 text[0] == '0' and
1020 (text.len == 1 or switch (tag) {
1021 .number_int => text[1] >= '0' and text[1] < '9',
1022 .number_float => text[1] != '.',
1023 else => @compileError(unreachable),
1024 });
1025
1026 if (has_zero_prefix) {
1027 try t.report.warn(.zero_prefixed_literal, .{ .token = t.ast.nodeToken(node) });
1028 }
1029
1030 return try t.addRef(.number, .{
1031 .src_node = ctx.nodeOffset(node),
1032 .extra_index = try t.extra.append(t.gpa, Float, Float.encode(value)),
1033 });
1034 },
1035
1036 .string => {
1037 const str = t.ast.basicNodeSlice(node);
1038 const sliced = try t.addDecodedSlicedString(str[1 .. str.len - 1]);
1039 return try t.addRef(.string, .{
1040 .src_node = ctx.nodeOffset(node),
1041 .extra_index = try t.extra.append(t.gpa, SlicedString, sliced),
1042 });
1043 },
1044
1045 .assign => {
1046 const ast_bin = t.ast.nodeData(node).bin;
1047 const target, var result_type = try t.transformAssignmentTarget(ctx, ast_bin.lhs) orelse
1048 return .void;
1049 const rhs = try t.transformExpression(ctx, &result_type, ast_bin.rhs) orelse
1050 return null;
1051 return try target.emit(t, rhs, ctx, node);
1052 },
1053
1054 inline .add,
1055 .sub,
1056 .mul,
1057 .div,
1058 .rem,
1059 .pow,
1060 .bit_and,
1061 .bit_or,
1062 .bit_xor,
1063 .shl,
1064 .shr,
1065 .eq,
1066 .ne,
1067 .lt,
1068 .le,
1069 .gt,
1070 .ge,
1071 .add_assign,
1072 .sub_assign,
1073 .mul_assign,
1074 .div_assign,
1075 .rem_assign,
1076 .pow_assign,
1077 .and_assign,
1078 .or_assign,
1079 .bit_and_assign,
1080 .bit_or_assign,
1081 .bit_xor_assign,
1082 .shl_assign,
1083 .shr_assign,
1084 => |tag| {
1085 // This code path implements binary operations, as well as the
1086 // compound compute + assign operation.
1087 const is_assign = comptime tag.isAssignBin();
1088 const teg_tag = @field(
1089 Hir.Tag,
1090 if (is_assign)
1091 @tagName(tag)[0 .. @tagName(tag).len - "_assign".len]
1092 else
1093 @tagName(tag),
1094 );
1095 const ast_bin = t.ast.nodeData(node).bin;
1096 var lhs_result_type: ResultType = .none;
1097 const lhs, var assign_result_type: ResultType = if (is_assign)
1098 try t.transformAssignmentTarget(ctx, ast_bin.lhs) orelse
1099 return .void
1100 else
1101 .{
1102 try t.transformExpression(ctx, &lhs_result_type, ast_bin.lhs) orelse
1103 return null,
1104 .none,
1105 };
1106 const rhs = try t.transformExpression(ctx, &assign_result_type, ast_bin.rhs) orelse return null;
1107
1108 const compute_inst = try t.addRef(teg_tag, .{
1109 .src_node = ctx.nodeOffset(node),
1110 .extra_index = try t.extra.append(t.gpa, Hir.Bin, .{
1111 .lhs = if (is_assign) try lhs.toRef(t) else lhs,
1112 .rhs = rhs,
1113 }),
1114 });
1115
1116 if (is_assign) {
1117 return try lhs.emit(t, compute_inst, ctx, node);
1118 }
1119
1120 return compute_inst;
1121 },
1122
1123 inline .negate,
1124 .bool_not,
1125 .bit_not,
1126 .array_type,
1127 => |tag| {
1128 const ast_un = t.ast.nodeData(node).un;
1129 const teg_tag = @field(Hir.Tag, @tagName(tag));
1130
1131 var operand_type: ResultType = switch (tag) {
1132 .negate => .none, // any numeric type allowed
1133 .bool_not => .{ .ref = .bool_type },
1134 .bit_not => .{ .ref = .int32_type },
1135 .array_type => .{ .ref = .type_type },
1136 .assert => .{ .ref = .bool_type },
1137 else => @compileError(unreachable),
1138 };
1139
1140 const operand = try t.transformExpression(ctx, &operand_type, ast_un.expr) orelse
1141 return null;
1142
1143 return try t.addRef(teg_tag, .{
1144 .src_node = ctx.nodeOffset(node),
1145 .operand = operand,
1146 });
1147 },
1148
1149 .@"comptime" => {
1150 const ast_un = t.ast.nodeData(node).un;
1151 const operand = try t.transformToBlock(ctx, ast_un.expr, .tracked_result, result, .compile_time);
1152 // Move the source index to the `comptime` node
1153 if (operand.index()) |index| {
1154 const slice = t.insts.slice();
1155 const data: *EncodedData = &slice.items(.data)[@intFromEnum(index)];
1156 var existing = data.decode(.block, slice.items(.tag)[@intFromEnum(index)]);
1157 existing.src_node = ctx.nodeOffset(node);
1158 data.* = .encode(.block, existing);
1159
1160 // Since the block was added in an untracked fashion, it
1161 // needs to be tracked it before returning.
1162 try t.scratch.add(t.arena, Inst.Index, index);
1163 }
1164 return operand;
1165 },
1166
1167 .assert => {
1168 const ast_un = t.ast.nodeData(node).un;
1169 const operand = try t.transformToBlock(ctx, ast_un.expr, .untracked_result, .{ .ref = .bool_type }, .run_time);
1170 // Move the source index to the `comptime` node
1171 if (operand.index()) |index| {
1172 const slice = t.insts.slice();
1173 const data: *EncodedData = &slice.items(.data)[@intFromEnum(index)];
1174 var existing = data.decode(.block, slice.items(.tag)[@intFromEnum(index)]);
1175 existing.src_node = ctx.nodeOffset(node);
1176 data.* = .encode(.block, existing);
1177 }
1178 return try t.addRef(.assert, .{
1179 .src_node = ctx.nodeOffset(node),
1180 .operand = operand,
1181 });
1182 },
1183
1184 .label => {
1185 const expr = t.ast.nodeData(node).label.expr;
1186 const label = t.ast.tokenSlice(t.ast.nodeToken(node));
1187
1188 var target: BlockContext.BreakTarget = .{
1189 .parent = ctx.break_target,
1190 .label = label,
1191 .is_loop = false,
1192 .result_type = result,
1193 };
1194 const inner_ctx = ctx.withBreak(&target);
1195
1196 const scratch = t.scratch.mark();
1197
1198 const inner_expr = try t.transformExpression(inner_ctx, result, expr);
1199
1200 // if there are no new instructions, we dont have to create a block context
1201 if (scratch.len() == 0)
1202 return inner_expr;
1203
1204 if (target.block_index) |block| {
1205 if (inner_expr) |out| {
1206 _ = try t.addRef(.@"break", .{
1207 .src_node = ctx.nodeOffset(node),
1208 .extra_index = try t.extra.append(t.gpa, Hir.Break, .{
1209 .block = block,
1210 .operand = out,
1211 }),
1212 });
1213 }
1214
1215 const content = try t.scratchToIndexList(scratch);
1216 scratch.deinit();
1217
1218 t.getInstData(block).* = .encode(.block, .{
1219 .src_node = ctx.nodeOffset(node),
1220 .extra_index = try t.extra.append(t.gpa, IndexList, content),
1221 });
1222
1223 try t.scratch.add(t.arena, Inst.Index, block);
1224
1225 return block.toRef();
1226 } else {
1227 std.debug.print("Warn, unused label {s}\n", .{label});
1228 }
1229
1230 return inner_expr;
1231 },
1232
1233 inline .block_void,
1234 .block_implicit_return,
1235 => |tag| {
1236 const statements = t.ast.nodeStatements(node);
1237
1238 var last: Inst.Ref = .void;
1239 for (statements, 1..) |statement, i| {
1240 const is_last = i == statements.len;
1241 last = try t.transformBlockStatement(
1242 ctx,
1243 statement,
1244 if (tag == .block_implicit_return and is_last)
1245 result
1246 else
1247 null,
1248 ) orelse {
1249 return null;
1250 };
1251 }
1252
1253 return if (tag == .block_implicit_return) last else .void;
1254 },
1255
1256 .builtin_call => {
1257 const builtin = Builtin.map.get(t.ast.basicNodeSlice(node)[1..]) orelse {
1258 try t.report.err(.invalid_builtin, .{ .node = node });
1259 return .void;
1260 };
1261
1262 const base = t.ast.nodeData(node).list;
1263 const args = base.slice(&t.ast);
1264
1265 return switch (builtin) {
1266 .@"extern" => Builtin.builtin_extern(t, ctx, node, args),
1267 .js_new => Builtin.builtin_jsNew(t, ctx, node, args),
1268 // .import => Builtin.import(t, decl, node, args),
1269 // .fetch => Builtin.fetch(t, decl, node, args),
1270 .compile_error => Builtin.builtin_compileError(t, ctx, node, args),
1271 .type_of => Builtin.builtin_typeOf(t, ctx, node, args),
1272 else => |tag| std.debug.panic("TODO builtin {s}", .{@tagName(tag)}),
1273 };
1274 },
1275
1276 .call => {
1277 const call = t.ast.nodeData(node).call;
1278 const args = t.ast.extraData(call.extra, Ast.Call).args.slice(&t.ast);
1279
1280 var base_result: ResultType = .none;
1281 const base = try t.transformExpression(ctx, &base_result, call.base) orelse return null;
1282
1283 const args_list: RefList = call_args: {
1284 const extra_top = try t.extra.alloc(t.gpa, args.len);
1285
1286 for (args, 0.., extra_top..) |arg, arg_id, index| {
1287 var arg_result: ResultType = .{ .argument = .{
1288 .call_base = base,
1289 .argument = @intCast(arg_id),
1290 } };
1291 t.extra.setU32(
1292 index,
1293 @intFromEnum(try t.transformExpression(ctx, &arg_result, arg) orelse
1294 return null),
1295 );
1296 }
1297
1298 break :call_args .{
1299 .start = @intCast(extra_top),
1300 .len = @intCast(args.len),
1301 };
1302 };
1303
1304 const call_inst = try t.addRef(.call, .{
1305 .src_node = ctx.nodeOffset(node),
1306 .extra_index = try t.extra.append(t.gpa, Hir.Call, .{
1307 .base = base,
1308 .args = args_list,
1309 }),
1310 });
1311
1312 return call_inst;
1313 },
1314
1315 .field => {
1316 const field = t.ast.nodeData(node).field;
1317
1318 var base_result: ResultType = .none;
1319 const base = try t.transformExpression(ctx, &base_result, field.base) orelse return null;
1320
1321 const rhs = switch (t.ast.tokens.items(.tag)[field.field_token]) {
1322 .id => rhs: {
1323 const id = t.ast.tokenSlice(field.field_token);
1324 const id_decoded = try t.decodeIdentifier(id);
1325 const prop_name_str = try t.addSlicedString(id_decoded);
1326
1327 break :rhs try t.addRef(.string, .{
1328 .src_node = ctx.nodeOffset(node),
1329 .extra_index = try t.extra.append(t.gpa, SlicedString, prop_name_str),
1330 });
1331 },
1332 .number_int => rhs: {
1333 const text = t.ast.tokenSlice(field.field_token);
1334
1335 break :rhs try t.addRef(.number, .{
1336 .src_node = ctx.nodeOffset(node),
1337 .extra_index = try t.extra.append(
1338 t.gpa,
1339 Float,
1340 Float.encode(std.fmt.parseFloat(f64, text) catch @panic("TODO")),
1341 ),
1342 });
1343 },
1344 else => unreachable, // `Parse` filters out all other token tags
1345 };
1346
1347 return try t.addRef(.field, .{
1348 .src_node = ctx.nodeOffset(node),
1349 .extra_index = try t.extra.append(t.gpa, Hir.Bin, .{
1350 .lhs = base,
1351 .rhs = rhs,
1352 }),
1353 });
1354 },
1355
1356 .index => {
1357 const bin = t.ast.nodeData(node).bin;
1358
1359 var lhs_result: ResultType = .none;
1360 const lhs = try t.transformExpression(ctx, &lhs_result, bin.lhs) orelse
1361 return null;
1362
1363 var rhs_result: ResultType = .none;
1364 const rhs = try t.transformExpression(ctx, &rhs_result, bin.rhs) orelse
1365 return null;
1366
1367 return try t.addRef(.field, .{
1368 .src_node = ctx.nodeOffset(node),
1369 .extra_index = try t.extra.append(t.gpa, Hir.Bin, .{
1370 .lhs = lhs,
1371 .rhs = rhs,
1372 }),
1373 });
1374 },
1375
1376 .@"if" => {
1377 const if_node = t.ast.nodeData(node).@"if";
1378 const if_data = t.ast.extraData(if_node.extra, Ast.If);
1379
1380 var cond_result: ResultType = .{ .ref = .bool_type };
1381 const cond = try t.transformExpression(ctx, &cond_result, if_node.cond) orelse
1382 return null;
1383
1384 var yes_result = result.*;
1385 const yes = try t.transformToBlock(ctx, if_data.yes, .tracked_result, &yes_result, .run_time);
1386
1387 var no_result: ResultType = if (if_data.no == .none) .used else result.*;
1388 const no = if (if_data.no.unwrap()) |no|
1389 try t.transformToBlock(ctx, no, .tracked_result, &no_result, .run_time)
1390 else
1391 Inst.Ref.void;
1392
1393 // Mark the incoming result type as used if both branches use the result type
1394 if (result.* != .none and yes_result == .none and no_result == .none) {
1395 result.* = .used;
1396 }
1397
1398 return try t.addRef(.@"if", .{
1399 .src_node = ctx.nodeOffset(node),
1400 .extra_index = try t.extra.append(t.gpa, Hir.If, .{
1401 .cond = cond,
1402 .yes = yes,
1403 .no = no,
1404 }),
1405 });
1406 },
1407
1408 .@"return" => {
1409 const ret_node = t.ast.nodeData(node).@"return";
1410
1411 const target = ctx.return_target orelse {
1412 try t.report.err(.invalid_return, .{ .node = node });
1413 return .void;
1414 };
1415
1416 var return_result = target.result_type;
1417
1418 const operand: Inst.Ref = if (ret_node.expr.unwrap()) |expr|
1419 try t.transformExpression(
1420 ctx.withReturn(null),
1421 &return_result,
1422 expr,
1423 ) orelse return null
1424 else
1425 .void;
1426
1427 _ = try t.addIndex(.@"break", .{
1428 .src_node = ctx.nodeOffset(node),
1429 .extra_index = try t.extra.append(t.gpa, Hir.Break, .{
1430 .block = target.block_index,
1431 .operand = operand,
1432 }),
1433 });
1434
1435 return null; // control flow ends
1436 },
1437
1438 .@"break" => {
1439 const brk_node = t.ast.nodeData(node).@"break";
1440 const label = t.ast.tokenSlice(
1441 brk_node.label.unwrap() orelse
1442 @panic("TODO: unlabelled breaks"),
1443 );
1444
1445 const block: *BlockContext.BreakTarget = ctx.lookupNamedBreak(label) orelse {
1446 try t.report.err(.unresolved_label, .{ .node = node });
1447 return null;
1448 };
1449
1450 var break_result = block.result_type.*;
1451 const operand: Inst.Ref = if (brk_node.value.unwrap()) |expr|
1452 try t.transformExpression(ctx, &break_result, expr) orelse
1453 return null
1454 else
1455 .void;
1456
1457 const coerced_operand = try t.maybeCoerce(ctx.nodeOffset(node), operand, &break_result);
1458
1459 _ = try t.addIndex(.@"break", .{
1460 .src_node = ctx.nodeOffset(node),
1461 .extra_index = try t.extra.append(t.gpa, Hir.Break, .{
1462 .block = try block.getInst(t),
1463 .operand = coerced_operand,
1464 }),
1465 });
1466
1467 return null; // control flow ends
1468 },
1469
1470 .struct_decl => {
1471 const struct_node = t.ast.fullStructDecl(node);
1472 const payload_index = try t.extra.alloc(t.gpa, struct_node.fields.len * 2);
1473
1474 for (
1475 struct_node.fields,
1476 payload_index..,
1477 payload_index + struct_node.fields.len..,
1478 ) |field_node, i, j| {
1479 const field = t.ast.fullStructField(field_node);
1480
1481 const id = try t.decodeIdentifier(field.id);
1482 const key = try t.addNullTerminatedString(id);
1483
1484 var type_result: ResultType = .{ .ref = .type_type };
1485 const value = try t.transformExpression(ctx, &type_result, field.type) orelse
1486 return null;
1487
1488 t.extra.setU32(i, @intFromEnum(key));
1489 t.extra.setU32(j, @intFromEnum(value));
1490 }
1491
1492 return try t.addRef(.struct_decl, .{
1493 .src_node = ctx.nodeOffset(node),
1494 .extra_index = try t.extra.append(t.gpa, Hir.StructDecl, .{
1495 .payload_index = payload_index,
1496 .field_len = @intCast(struct_node.fields.len),
1497 }),
1498 });
1499 },
1500
1501 .struct_init => {
1502 const struct_node = t.ast.fullStructInit(node);
1503 // TODO: use t.scratch for this
1504 const payload_index = try t.extra.alloc(t.gpa, struct_node.fields.len * 2);
1505
1506 var result_type: ResultType = .{ .ref = .type_type };
1507 const base_type = if (struct_node.type) |ty|
1508 try t.transformExpression(ctx, &result_type, ty) orelse
1509 return null
1510 else
1511 (try result.consume(t)) orelse {
1512 try t.report.err(.missing_result_type, .{ .node = node });
1513 return .void;
1514 };
1515
1516 for (
1517 struct_node.fields,
1518 payload_index..,
1519 payload_index + struct_node.fields.len..,
1520 ) |field_node, i, j| {
1521 const field = t.ast.fullStructField(field_node);
1522
1523 const id = try t.decodeIdentifier(field.id);
1524 const field_name = try t.addNullTerminatedString(id);
1525
1526 var field_result_type: ResultType = .{ .struct_field = .{
1527 .type = base_type,
1528 .field = field_name,
1529 } };
1530 const field_value = (try t.transformExpression(
1531 ctx,
1532 &field_result_type,
1533 field.type,
1534 )) orelse return null;
1535
1536 t.extra.setU32(i, @intFromEnum(field_name));
1537 t.extra.setU32(j, @intFromEnum(
1538 field_value,
1539 ));
1540 }
1541
1542 return try t.addRef(.struct_init, .{
1543 .src_node = ctx.nodeOffset(node),
1544 .extra_index = try t.extra.append(t.gpa, Hir.StructInit, .{
1545 .base_type = base_type,
1546 .payload_index = payload_index,
1547 .field_len = @intCast(struct_node.fields.len),
1548 }),
1549 });
1550 },
1551
1552 .array_init => {
1553 const array_init = t.ast.nodeData(node).list;
1554
1555 const array_type = try result.consume(t);
1556
1557 const items = try t.transformExpressionList(
1558 ctx,
1559 if (array_type) |ty| .{ .array_value = ty } else .none,
1560 array_init.slice(&t.ast),
1561 ) orelse return null;
1562
1563 if (array_type == null and items.len == 0) {
1564 return .empty_array;
1565 }
1566
1567 return try t.addRef(.array_init, .{
1568 .src_node = ctx.nodeOffset(node),
1569 .extra_index = try t.extra.append(t.gpa, Hir.ArrayInit, .{
1570 .type_hint = if (array_type) |ty| ty.toOptional() else .none,
1571 .list = items,
1572 }),
1573 });
1574 },
1575
1576 .tuple_init => {
1577 const tuple_init = t.ast.nodeData(node).list;
1578
1579 const items = try t.transformExpressionList(ctx, .none, tuple_init.slice(&t.ast)) orelse return null;
1580
1581 if (items.len == 0) {
1582 return .empty_tuple;
1583 }
1584
1585 return try t.addRef(.tuple_init, .{
1586 .src_node = ctx.nodeOffset(node),
1587 .extra_index = try t.extra.append(t.gpa, Hir.TupleInit, .{
1588 .type_hint = .none,
1589 .items = items,
1590 }),
1591 });
1592 },
1593
1594 .tuple_type => {
1595 const tuple_type = t.ast.nodeData(node).list;
1596
1597 const items = try t.transformExpressionList(ctx, .none, tuple_type.slice(&t.ast)) orelse return null;
1598
1599 return try t.addRef(.tuple_type, .{
1600 .src_node = ctx.nodeOffset(node),
1601 .extra_index = try t.extra.append(t.gpa, Hir.TupleType, .{
1602 .items = items,
1603 }),
1604 });
1605 },
1606
1607 inline .fn_decl, .fn_expr => |tag| {
1608 const data = switch (tag) {
1609 .fn_decl => t.ast.fullFnDecl(node),
1610 .fn_expr => t.ast.fullFnExpr(node),
1611 else => @compileError(unreachable),
1612 };
1613
1614 try t.pushScope();
1615 defer t.popScope();
1616
1617 try t.pushCaptureScope(@enumFromInt(t.insts.len - 1));
1618 defer t.popCaptureScope();
1619
1620 const param_list = brk: {
1621 const scratch = t.scratch.mark();
1622 defer scratch.deinit();
1623
1624 for (data.params) |param| {
1625 try t.scratch.add(t.arena, Inst.Ref, try t.transformParamDecl(ctx, param));
1626 }
1627
1628 break :brk try t.scratchToOptionalRefList(scratch);
1629 };
1630
1631 const return_type = if (data.return_type) |return_type_node|
1632 try t.transformToBlock(
1633 ctx.withReturn(null),
1634 return_type_node,
1635 .untracked_result,
1636 .{ .ref = .type_type },
1637 .run_time,
1638 )
1639 else
1640 null;
1641
1642 const body = try t.transformToBlock(
1643 ctx,
1644 data.body,
1645 .function_body,
1646 if (return_type) |ref|
1647 .{ .ref = ref }
1648 else
1649 .none,
1650 .run_time,
1651 );
1652
1653 const inst_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(body);
1654
1655 const capture_scope = t.captureScope();
1656 const captures_len = capture_scope.symbol_uses.count();
1657 const captures_data = try t.extra.alloc(t.gpa, captures_len);
1658 @memcpy(
1659 t.extra.list.items[captures_data..][0..captures_len],
1660 @as([]u32, @ptrCast(capture_scope.symbol_uses.entries.items(.key))),
1661 );
1662
1663 return try t.addRef(.func, .{
1664 .src_node = ctx.nodeOffset(node),
1665 .extra_index = try t.extra.append(t.gpa, Hir.Func, .{
1666 .params = param_list,
1667 .return_type = if (return_type) |ref| ref.toOptional() else .none,
1668 .body = body,
1669 .instruction_count = inst_count,
1670 .captures = .{ .start = captures_data, .len = @intCast(captures_len) },
1671 }),
1672 });
1673 },
1674
1675 .multiline_string => {
1676 const token_list = t.ast.nodeData(node).list;
1677
1678 assert(t.id_decode_buf.items.len == 0);
1679 defer t.id_decode_buf.clearRetainingCapacity();
1680
1681 try t.id_decode_buf.appendSlice(t.ast.tokenSlice(token_list.start)[2..]);
1682 for (token_list.start + 1..token_list.start + token_list.len) |i| {
1683 try t.id_decode_buf.append('\n');
1684 try t.id_decode_buf.appendSlice(t.ast.tokenSlice(@intCast(i))[2..]);
1685 }
1686
1687 return try t.addRef(.string, .{
1688 .src_node = ctx.nodeOffset(node),
1689 .extra_index = try t.extra.append(
1690 t.gpa,
1691 SlicedString,
1692 try t.addSlicedString(t.id_decode_buf.items),
1693 ),
1694 });
1695 },
1696
1697 .bool_and => {
1698 const bin = t.ast.nodeData(node).bin;
1699
1700 var lhs_result: ResultType = .{ .ref = .bool_type };
1701 const lhs = try t.maybeCoerce(
1702 ctx.nodeOffset(node),
1703 try t.transformExpression(ctx, &lhs_result, bin.lhs) orelse
1704 return null,
1705 &lhs_result,
1706 );
1707
1708 const rhs = try t.transformToBlock(ctx, bin.rhs, .untracked_result, .{ .ref = .bool_type }, .run_time);
1709
1710 return try t.addRef(.@"if", .{
1711 .src_node = ctx.nodeOffset(node),
1712 .extra_index = try t.extra.append(t.gpa, Hir.If, .{
1713 .cond = lhs,
1714 .yes = rhs,
1715 .no = .bool_false,
1716 }),
1717 });
1718 },
1719
1720 .bool_or => {
1721 const bin = t.ast.nodeData(node).bin;
1722
1723 var lhs_result: ResultType = .{ .ref = .bool_type };
1724 const lhs = try t.maybeCoerce(
1725 ctx.nodeOffset(node),
1726 try t.transformExpression(ctx, &lhs_result, bin.lhs) orelse
1727 return null,
1728 &lhs_result,
1729 );
1730
1731 const rhs = try t.transformToBlock(ctx, bin.rhs, .untracked_result, .{ .ref = .bool_type }, .run_time);
1732
1733 return try t.addRef(.@"if", .{
1734 .src_node = ctx.nodeOffset(node),
1735 .extra_index = try t.extra.append(t.gpa, Hir.If, .{
1736 .cond = lhs,
1737 .yes = .bool_true,
1738 .no = rhs,
1739 }),
1740 });
1741 },
1742
1743 else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}),
1744 }
1745}
1746
1747fn transformExpressionList(t: *Transform, decl: BlockContext, result: ResultType, args: []const NodeIndex) !?RefList {
1748 const extra_top = try t.extra.alloc(t.gpa, args.len);
1749
1750 for (args, extra_top..) |arg, i| {
1751 var result_mut = result;
1752 t.extra.setU32(
1753 i,
1754 @intFromEnum(try t.transformExpression(decl, &result_mut, arg) orelse
1755 return null),
1756 );
1757 }
1758
1759 return .{
1760 .start = @intCast(extra_top),
1761 .len = @intCast(args.len),
1762 };
1763}
1764
1765/// The left-hand side of an assignment is known as the "assignment target".
1766/// This returns the assignment target's Ref, as well as a result type to visit
1767/// the right-hand side with. Note that only a few expressions are valid here:
1768///
1769/// - A plain identifier with a `memory` instruction -> `.store_memory`
1770/// - A plain identifier to a .variable_declaration -> `.store_decl`
1771/// - A `field` or `index` node -> `.store_field`
1772///
1773/// Note that these three are the root nodes, it is legal to have:
1774///
1775/// get().x = 1; // `field` of a `call` node
1776///
1777/// In the end, the assignment target must comprise either a ref to a `memory`
1778/// or a value with a reference type; and optionally a sub property.
1779fn transformAssignmentTarget(
1780 t: *Transform,
1781 ctx: BlockContext,
1782 node: NodeIndex,
1783) Allocator.Error!?struct { AssignmentTarget, ResultType } {
1784 const node_tag = t.ast.nodes.items(.tag)[node];
1785
1786 const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)});
1787 defer trace.end();
1788
1789 switch (node_tag) {
1790 .root,
1791 .param_decl,
1792 .var_decl,
1793 => unreachable, // not an expression
1794
1795 .id => {
1796 const id = try t.decodeIdentifier(t.ast.basicNodeSlice(node));
1797 assert(!mem.eql(u8, id, "_")); // discards are not assignment targets
1798
1799 if (id.len == 0 or mem.indexOfScalar(u8, id, '\x00') != null) {
1800 try t.report.err(.invalid_string_identifier, .{ .node = node });
1801 return null;
1802 }
1803
1804 if (built_in_ids.has(id)) {
1805 try t.report.err(.assign_builtin, .{ .node = node });
1806 return null;
1807 }
1808
1809 const lookup: *Symbol = t.lookupId(id) orelse {
1810 try t.report.err(.unknown_identifier, .{ .node = node });
1811 return null;
1812 };
1813
1814 return switch (lookup.kind) {
1815 .mutable => {
1816 lookup.flags.mutated = true;
1817 return .{
1818 .{ .memory = lookup.value.mutable },
1819 lookup.resultType(),
1820 };
1821 },
1822 .variable_declaration => {
1823 return .{
1824 .{ .variable_declaration = .{
1825 .src_node = ctx.nodeOffset(node),
1826 .string = try t.addNullTerminatedString(id),
1827 } },
1828 lookup.resultType(),
1829 };
1830 },
1831 else => {
1832 lookup.flags.used = true;
1833
1834 try t.report.err(.invalid_assignment_symbol, .{
1835 .node = node,
1836 .decl = lookup.src_node,
1837 .kind = switch (lookup.kind) {
1838 .mutable, .variable_declaration => unreachable,
1839 .declaration => .declaration,
1840 .param => .param,
1841 .immutable => switch (t.ast.nodeTag(lookup.src_node)) {
1842 .param_decl => .param,
1843 else => .constant,
1844 },
1845 },
1846 });
1847
1848 return null;
1849 },
1850 };
1851 },
1852
1853 .field => {
1854 const field = t.ast.nodeData(node).field;
1855
1856 var no_result: ResultType = .none;
1857 const base = try t.transformExpression(ctx, &no_result, field.base) orelse return null;
1858
1859 const field_str = try t.decodeIdentifier(t.ast.tokenSlice(field.field_token));
1860 const field_sliced = try t.addSlicedString(field_str);
1861
1862 const field_ref = try t.addRef(.string, .{
1863 .src_node = ctx.nodeOffset(node),
1864 .extra_index = try t.extra.append(t.gpa, SlicedString, field_sliced),
1865 });
1866
1867 return .{
1868 .{ .field = .{
1869 .src_node = ctx.nodeOffset(node),
1870 .base = base,
1871 .field = field_ref,
1872 } },
1873 .none, // TODO:
1874 };
1875 },
1876
1877 .index => {
1878 const bin = t.ast.nodeData(node).bin;
1879
1880 var no_result: ResultType = .none;
1881 const base = try t.transformExpression(ctx, &no_result, bin.lhs) orelse return null;
1882 const field = try t.transformExpression(ctx, &no_result, bin.rhs) orelse return null;
1883
1884 return .{
1885 .{ .field = .{
1886 .src_node = ctx.nodeOffset(node),
1887 .base = base,
1888 .field = field,
1889 } },
1890 .none, // TODO:
1891 };
1892 },
1893
1894 else => {
1895 var result_type: ResultType = .none;
1896 _ = try t.transformExpression(ctx, &result_type, node);
1897
1898 try t.report.err(.invalid_assignment_target, .{
1899 .node = node,
1900 });
1901
1902 return null;
1903 },
1904 }
1905}
1906
1907const AssignmentTarget = union(enum) {
1908 memory: Inst.Index,
1909 variable_declaration: struct {
1910 src_node: i32,
1911 string: NullTerminatedString,
1912 },
1913 field: struct {
1914 src_node: i32,
1915 base: Inst.Ref,
1916 field: Inst.Ref,
1917 },
1918
1919 fn toRef(target: AssignmentTarget, t: *Transform) !Inst.Ref {
1920 return switch (target) {
1921 .memory => |memory| memory.toRef(),
1922 .variable_declaration => |var_decl| try t.addRef(.load_decl, .{
1923 .src_node = var_decl.src_node,
1924 .string = var_decl.string,
1925 }),
1926 .field => |field| try t.addRef(.field, .{
1927 .src_node = field.src_node,
1928 .extra_index = try t.extra.append(t.gpa, Hir.Bin, .{
1929 .lhs = field.base,
1930 .rhs = field.field,
1931 }),
1932 }),
1933 };
1934 }
1935
1936 fn emit(target: AssignmentTarget, t: *Transform, rhs: Inst.Ref, ctx: BlockContext, node: NodeIndex) !Inst.Ref {
1937 return switch (target) {
1938 .memory => |memory| try t.addRef(.store, .{
1939 .src_node = ctx.nodeOffset(node),
1940 .extra_index = try t.extra.append(t.gpa, Hir.Store, .{
1941 .memory = memory,
1942 .operand = rhs,
1943 }),
1944 }),
1945 .variable_declaration => |var_decl| try t.addRef(.store_decl, .{
1946 .src_node = ctx.nodeOffset(node),
1947 .extra_index = try t.extra.append(t.gpa, Hir.StoreVarDecl, .{
1948 .var_decl = var_decl.string,
1949 .operand = rhs,
1950 }),
1951 }),
1952 .field => |field| try t.addRef(.store_field, .{
1953 .src_node = ctx.nodeOffset(node),
1954 .extra_index = try t.extra.append(t.gpa, Hir.StoreField, .{
1955 .base = field.base,
1956 .field = field.field,
1957 .operand = rhs,
1958 }),
1959 }),
1960 };
1961 }
1962};
1963
1964fn transformBlockStatement(
1965 t: *Transform,
1966 ctx: BlockContext,
1967 node: NodeIndex,
1968 maybe_result: ?*ResultType,
1969) !?Inst.Ref {
1970 var void_result_storage: ResultType = .{ .ref = .void };
1971 const result = maybe_result orelse &void_result_storage;
1972
1973 const node_tag = t.ast.nodes.items(.tag)[node];
1974
1975 const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)});
1976 defer trace.end();
1977
1978 switch (node_tag) {
1979 .root => unreachable, // cannot appear in a block
1980 .param_decl => unreachable, // cannot appear in a block
1981
1982 .var_decl => {
1983 const var_decl = t.ast.fullVarDecl(node);
1984
1985 var id, const id_allocated = try t.decodeIdentifierExtra(var_decl.id);
1986 if (id.len == 0 or mem.indexOfScalar(u8, id, '\x00') != null) {
1987 try t.report.err(.invalid_string_identifier, .{ .node = node });
1988 return .void;
1989 }
1990
1991 if (mem.eql(u8, id, "_")) {
1992 try t.report.warn(.invalid_discard, .{ .node = node });
1993
1994 const ref = switch (t.ast.nodeTag(var_decl.init)) {
1995 .id => {
1996 const inner_id = try t.decodeIdentifier(t.ast.basicNodeSlice(var_decl.init));
1997 if (inner_id.len == 0 or mem.indexOfScalar(u8, inner_id, '\x00') != null) {
1998 try t.report.err(.invalid_string_identifier, .{ .node = var_decl.init });
1999 return .void;
2000 }
2001
2002 if (built_in_ids.has(inner_id)) {
2003 try t.report.warn(.discard_builtin, .{ .node = var_decl.init });
2004 return .void;
2005 }
2006
2007 const lookup = t.lookupId(inner_id) orelse {
2008 try t.report.err(.unknown_identifier, .{ .node = var_decl.init });
2009 return .void;
2010 };
2011
2012 lookup.discarded = .init(node);
2013
2014 return .void;
2015 },
2016 else => ref: {
2017 var empty_type: ResultType = .none;
2018 break :ref try t.transformExpression(ctx, &empty_type, var_decl.init) orelse
2019 return null;
2020 },
2021 };
2022
2023 return try t.addRef(.discard, .{
2024 .src_node = ctx.nodeOffset(node),
2025 .operand = ref,
2026 });
2027 }
2028
2029 if (id_allocated) {
2030 // consider arena?
2031 id = try t.gpa.dupe(u8, id);
2032 } else {
2033 // assert that the memory is in the source code. this is how
2034 // we can identify later if it is owned or not.
2035 assert(isSliceInBuffer(u8, t.ast.utf8_source, id));
2036 }
2037
2038 if (built_in_ids.has(id)) {
2039 try t.report.err(.shadow_builtin_variable, .{ .node = node });
2040 return .void;
2041 }
2042
2043 var type_result: ResultType = .{ .ref = .type_type };
2044 const type_ref = if (var_decl.type) |data_type|
2045 try t.transformExpression(ctx, &type_result, data_type)
2046 else
2047 null;
2048
2049 var value_result: ResultType = if (type_ref) |ty|
2050 .{ .ref = ty }
2051 else
2052 .none;
2053
2054 const data_ref = try t.transformExpression(ctx, &value_result, var_decl.init) orelse
2055 return null;
2056
2057 // Add a .coerce instruction if there was a type declared.
2058 const data_coerced_ref = coerced: {
2059 if (type_ref) |ref| {
2060 // We can avoid the coercion instruction if the result type was used.
2061 if (value_result != .used) {
2062 break :coerced try t.addRef(.coercion, .{
2063 .src_node = ctx.nodeOffset(node),
2064 .extra_index = try t.extra.append(t.gpa, Hir.Coercion, .{
2065 .type = ref,
2066 .operand = data_ref,
2067 }),
2068 });
2069 }
2070 }
2071
2072 break :coerced data_ref;
2073 };
2074
2075 const gop = try t.scope().symbols.getOrPut(t.gpa, id);
2076
2077 // if re-declaring, apply unused warnings now
2078 if (gop.found_existing) {
2079 if (id_allocated) t.gpa.free(id);
2080 try gop.value_ptr.addWarnings(t);
2081 }
2082
2083 gop.value_ptr.* = .{
2084 .src_node = node,
2085 .kind = if (var_decl.is_const) .immutable else .mutable,
2086 .value = if (var_decl.is_const)
2087 .{ .immutable = data_coerced_ref }
2088 else
2089 .{ .mutable = try t.addIndex(.memory, .{
2090 .src_node = ctx.nodeOffset(node),
2091 .operand = data_coerced_ref,
2092 }) },
2093 .discarded = .none,
2094 .flags = .{
2095 .result_type_kind = if (type_ref != null) .present else .missing,
2096 .used = false,
2097 .mutated = false,
2098 },
2099 .result_type_ref = if (type_ref) |ty| ty else data_ref,
2100 };
2101
2102 return .void;
2103 },
2104
2105 .fn_decl,
2106 .struct_decl,
2107 => |tag| {
2108 std.debug.panic("TODO: block statement {s}", .{@tagName(tag)});
2109 },
2110
2111 .assign,
2112 => {
2113 const bin = t.ast.nodeData(node).bin;
2114 const lhs = bin.lhs;
2115
2116 if (t.ast.nodeTag(lhs) == .id and
2117 mem.eql(u8, t.ast.basicNodeSlice(lhs), "_"))
2118 {
2119 // discard, instead of appending an .ensure_void check,
2120 // we will add a .discard instruction instead.
2121 // this will emit a warning if given void.
2122 var result_type: ResultType = .none;
2123 const value = try t.transformExpression(ctx, &result_type, bin.rhs) orelse
2124 return null;
2125
2126 return try t.addRef(.discard, .{
2127 .src_node = ctx.nodeOffset(node),
2128 .operand = value,
2129 });
2130 } else {
2131 // process this assignment as usual, do not append .ensure_void
2132 return try t.transformExpression(ctx, result, node);
2133 }
2134 },
2135
2136 .add_assign,
2137 .sub_assign,
2138 .mul_assign,
2139 .div_assign,
2140 .rem_assign,
2141 .pow_assign,
2142 .and_assign,
2143 .or_assign,
2144 .bit_and_assign,
2145 .bit_or_assign,
2146 .bit_xor_assign,
2147 .shl_assign,
2148 .shr_assign,
2149 => {
2150 // do not append a .ensure_void check
2151 return try t.transformExpression(ctx, result, node);
2152 },
2153
2154 else => {
2155 const value = try t.transformExpression(ctx, result, node) orelse
2156 return null;
2157
2158 if (maybe_result == null and value != .void) {
2159 if (t.ast.nodeTag(node) == .id) {
2160 try t.report.warn(.dangling_identifier, .{
2161 .node = node,
2162 });
2163 }
2164
2165 return try t.addRef(.ensure_void, .{
2166 .src_node = ctx.nodeOffset(node),
2167 .operand = value,
2168 });
2169 }
2170
2171 return value;
2172 },
2173 }
2174}
2175
2176fn addUntracked(t: *Transform, comptime tag: Hir.Tag, data: tag.Data()) !Inst.Index {
2177 try t.insts.append(t.gpa, .{
2178 .tag = tag,
2179 .data = .encode(tag, data),
2180 });
2181 return @enumFromInt(t.insts.len - 1);
2182}
2183
2184fn addIndex(t: *Transform, comptime tag: Hir.Tag, data: tag.Data()) !Inst.Index {
2185 const index = try t.addUntracked(tag, data);
2186 try t.scratch.add(t.arena, Inst.Index, index);
2187 return index;
2188}
2189
2190fn addRef(t: *Transform, comptime tag: Hir.Tag, data: tag.Data()) !Inst.Ref {
2191 return (try t.addIndex(tag, data)).toRef();
2192}
2193
2194fn pushScope(t: *Transform) !void {
2195 try t.scopes.append(t.arena, .{});
2196}
2197
2198fn popScope(t: *Transform) void {
2199 t.scope().deinit(t);
2200 t.scopes.items[t.scopes.items.len - 1] = undefined;
2201 t.scopes.items.len -= 1;
2202}
2203
2204fn scope(t: *Transform) *Scope {
2205 return &t.scopes.items[t.scopes.items.len - 1];
2206}
2207
2208fn pushCaptureScope(t: *Transform, start_index: Inst.Index) !void {
2209 try t.capture_scopes.append(t.arena, .{ .start_index = start_index });
2210}
2211
2212fn popCaptureScope(t: *Transform) void {
2213 t.captureScope().deinit(t);
2214 t.capture_scopes.items[t.capture_scopes.items.len - 1] = undefined;
2215 t.capture_scopes.items.len -= 1;
2216}
2217
2218fn captureScope(t: *Transform) *CaptureScope {
2219 return &t.capture_scopes.items[t.capture_scopes.items.len - 1];
2220}
2221
2222fn markCapturedRef(t: *Transform, ref: Inst.Ref) !void {
2223 switch (ref.decode()) {
2224 .value => {},
2225 .index => |idx| {
2226 var i = t.capture_scopes.items.len;
2227 while (i > 0) {
2228 i -= 1;
2229 const s = &t.capture_scopes.items[i];
2230
2231 if (@intFromEnum(idx) > @intFromEnum(s.start_index))
2232 break;
2233
2234 try s.symbol_uses.put(t.arena, idx, {});
2235 }
2236 },
2237 }
2238}
2239
2240fn getInstData(t: *Transform, index: Inst.Index) *EncodedData {
2241 return &t.insts.items(.data)[@intFromEnum(index)];
2242}
2243
2244fn maybeCoerce(t: *Transform, node_offset: i32, operand: Inst.Ref, result: *ResultType) !Inst.Ref {
2245 return switch (result.*) {
2246 .none, .used => operand,
2247 else => try t.addRef(.coercion, .{
2248 .src_node = node_offset,
2249 .extra_index = try t.extra.append(t.gpa, Hir.Coercion, .{
2250 .type = try result.consume(t) orelse
2251 unreachable, // `.none` already handled
2252 .operand = operand,
2253 }),
2254 }),
2255 };
2256}
2257
2258fn scratchToRefList(t: *Transform, scratch_top: core.U32Stack.Mark) !RefList {
2259 const items = scratch_top.slice(u32);
2260 const extra_top = t.extra.list.items.len;
2261 try t.extra.list.appendSlice(t.gpa, items);
2262 return .{
2263 .start = @intCast(extra_top),
2264 .len = @intCast(items.len),
2265 };
2266}
2267
2268fn scratchToIndexList(t: *Transform, scratch: core.U32Stack.Mark) !IndexList {
2269 const items = scratch.slice(u32);
2270 const extra_top = t.extra.list.items.len;
2271 try t.extra.list.appendSlice(t.gpa, items);
2272 return .{
2273 .start = @intCast(extra_top),
2274 .len = @intCast(items.len),
2275 };
2276}
2277
2278fn scratchToOptionalRefList(t: *Transform, scratch_top: core.U32Stack.Mark) !OptionalRefList {
2279 const items = scratch_top.slice(u32);
2280 const extra_top = t.extra.list.items.len;
2281 try t.extra.list.appendSlice(t.gpa, items);
2282 return .{
2283 .start = @intCast(extra_top),
2284 .len = @intCast(items.len),
2285 };
2286}
2287
2288fn lookupId(t: *Transform, id: []const u8) ?*Symbol {
2289 var i = t.scopes.items.len;
2290 while (i > 0) {
2291 i -= 1;
2292 if (t.scopes.items[i].symbols.getPtr(id)) |sym| {
2293 return sym;
2294 }
2295 }
2296 return null;
2297}
2298
2299fn addNullTerminatedString(t: *Transform, str: []const u8) !NullTerminatedString {
2300 if (str.len == 0) {
2301 return .empty;
2302 }
2303
2304 // loop through strings trying to find an existing match, and dedupe memory
2305 var pos: usize = 0;
2306 while (mem.indexOfScalarPos(u8, str, pos, 0)) |i| : (pos = i + 1) {
2307 const start_index = i - str.len;
2308 if (mem.eql(u8, t.string_bytes.items[start_index..][0..str.len :0], str)) {
2309 const string: NullTerminatedString = @enumFromInt(pos);
2310 assert(mem.eql(u8, string.slice(t.string_bytes.items), str));
2311 return string;
2312 }
2313 }
2314
2315 const string: NullTerminatedString = @enumFromInt(t.string_bytes.items.len);
2316 try t.string_bytes.ensureUnusedCapacity(t.gpa, str.len + 1);
2317 t.string_bytes.appendSliceAssumeCapacity(str);
2318 t.string_bytes.appendAssumeCapacity(0);
2319 assert(mem.eql(u8, string.slice(t.string_bytes.items), str));
2320 return string;
2321}
2322
2323fn addSlicedString(t: *Transform, str: []const u8) !SlicedString {
2324 if (str.len == 0) {
2325 return .{ .start = 0, .len = 0 };
2326 }
2327
2328 // loop through strings trying to find an existing match, and dedupe memory
2329 var pos: usize = 0;
2330 while (mem.indexOfScalarPos(u8, str, pos, 0)) |i| : (pos = i + 1) {
2331 const start_index = i - str.len - 1;
2332 if (mem.eql(u8, t.string_bytes.items[start_index..][0..str.len], str)) {
2333 const string: SlicedString = .{
2334 .start = @intCast(start_index),
2335 .len = @intCast(str.len),
2336 };
2337 assert(mem.eql(u8, string.slice(t.string_bytes.items), str));
2338 return string;
2339 }
2340 }
2341
2342 const string: SlicedString = .{
2343 .start = @intCast(t.string_bytes.items.len),
2344 .len = @intCast(str.len),
2345 };
2346 try t.string_bytes.ensureUnusedCapacity(t.gpa, str.len);
2347 t.string_bytes.appendSliceAssumeCapacity(str);
2348 assert(mem.eql(u8, string.slice(t.string_bytes.items), str));
2349 return string;
2350}
2351
2352fn addDecodedSlicedString(t: *Transform, escaped: []const u8) !SlicedString {
2353 const decoded = if (Tokenizer.stringHasEscapes(escaped)) brk: {
2354 const len = Tokenizer.decodeStringCount(escaped);
2355
2356 // Use the scratch arraylist as a temporary storage for decoding the
2357 // string. this is done so that duplicate strings will get de-duplicated
2358 // in string_bytes. It is more likely that there will be remaining bytes
2359 // in `.scratch` vs `.id_decode_buf`
2360 try t.scratch.list.ensureUnusedCapacity(t.gpa, @divFloor(len, @sizeOf(u32)) + 1);
2361 const buf = mem.sliceAsBytes(t.scratch.list.unusedCapacitySlice());
2362
2363 break :brk Tokenizer.decodeStringBuf(escaped, buf);
2364 } else escaped;
2365
2366 return t.addSlicedString(decoded);
2367}
2368
2369/// Decodes an identifier, storing it into a buffer reused across the transform
2370/// pass Memory is essentially free for this operation, at the cost of the
2371/// return value only being valid until the next call.
2372fn decodeIdentifierExtra(t: *Transform, raw_token: []const u8) !struct { []const u8, bool } {
2373 assert(raw_token.len > 0);
2374
2375 if (raw_token[0] != '@') {
2376 return .{ raw_token, false };
2377 }
2378
2379 assert(raw_token[1] == '"');
2380 assert(raw_token[raw_token.len - 1] == '"');
2381
2382 const inner = raw_token[2 .. raw_token.len - 1];
2383 const len = Tokenizer.decodeStringCount(inner);
2384
2385 try t.id_decode_buf.ensureTotalCapacityPrecise(len);
2386 @memset(t.id_decode_buf.items, undefined);
2387 t.id_decode_buf.items.len = len;
2388
2389 return .{
2390 Tokenizer.decodeStringBuf(inner, t.id_decode_buf.items),
2391 true,
2392 };
2393}
2394
2395fn decodeIdentifier(t: *Transform, raw_token: []const u8) ![]const u8 {
2396 return (try t.decodeIdentifierExtra(raw_token)).@"0";
2397}
2398
2399fn isSliceInBuffer(comptime T: type, haystack: []const T, needle: []const T) bool {
2400 return (@intFromPtr(haystack.ptr) <=
2401 @intFromPtr(needle.ptr) and
2402 (@intFromPtr(needle.ptr) + needle.len * @sizeOf(T)) <=
2403 (@intFromPtr(haystack.ptr) + haystack.len * @sizeOf(T)));
2404}
2405
2406const Transform = @This();
2407const std = @import("std");
2408const mem = std.mem;
2409const Allocator = mem.Allocator;
2410const assert = std.debug.assert;
2411
2412const core = @import("mod.zig");
2413const Hir = core.Hir;
2414const Inst = Hir.Inst;
2415const NullTerminatedString = Hir.NullTerminatedString;
2416const SlicedString = Hir.SlicedString;
2417const RefList = Hir.RefList;
2418const IndexList = Hir.IndexList;
2419const OptionalRefList = Hir.OptionalRefList;
2420const EncodedData = Hir.EncodedData;
2421const ExtraIndex = Hir.ExtraIndex;
2422
2423const array = core.array;
2424const ExtraStore = array.ExtraStore(.hir, .@"var");
2425const Float = array.Float;
2426
2427const Tokenizer = core.Tokenizer;
2428const Token = Tokenizer.Token;
2429const ByteOffset = Tokenizer.ByteOffset;
2430
2431const Ast = core.Ast;
2432const Unused = Ast.Unused;
2433const Node = Ast.Node;
2434const Data = Node.Data;
2435const SubList = Ast.SubList;
2436const NodeIndex = Ast.NodeIndex;
2437const TokenIndex = Ast.TokenIndex;
2438const OptionalNodeIndex = Ast.OptionalNodeIndex;
2439const OptionalTokenIndex = Ast.OptionalTokenIndex;
2440const OptionalExtraIndex = Ast.OptionalExtraIndex;
2441const optionalTokenIndex = OptionalTokenIndex.init;
2442const optionalNodeIndex = OptionalNodeIndex.init;
2443const optionalExtraIndex = OptionalExtraIndex.init;
2444
2445const InternPool = core.InternPool;
2446
2447const Report = core.Report;
2448const Comp = core.Comp;
2449
2450const tracer = @import("tracer");