1//! A Report is a list of errors and warnings for a part of a compilation.
2//!
3//! `Err/Warn` structs are unions of every possible message, the logic
4//! for printing them is all done here.
5//!
6//! Across the compiler, `Report.Builder`s' `.err` and `.warn` methods
7//! can add messages by ID.
8errors: []Item(Err),
9warnings: []Item(Warn),
10string_bytes: []u8,
11
12pub const empty: Report = .{
13 .errors = &.{},
14 .warnings = &.{},
15 .string_bytes = &.{},
16};
17
18pub var debug_show_report_trace: if (builtin.mode == .Debug) bool else void =
19 if (builtin.mode == .Debug) false;
20
21pub fn Item(T: type) type {
22 return struct {
23 data: T,
24 debug_stack_trace: DebugStoredTrace = .empty,
25 };
26}
27
28pub const Err = union(enum) {
29 pub const FieldEnum = core.FieldEnum(Err);
30
31 /// Constructs a `Report` with just a single error.
32 pub fn toReport(err: Err, alloc: Allocator, trace: DebugStoredTrace) !Report {
33 const errors = try alloc.dupe(Item(Err), &.{.{
34 .data = err,
35 .debug_stack_trace = trace,
36 }});
37 return .{ .errors = errors, .warnings = &.{}, .string_bytes = &.{} };
38 }
39
40 ast: @import("Parse.zig").AstError,
41
42 user_compile_error: struct {
43 src: LazyNode,
44 msg: String,
45
46 fn build(data: @This(), ctx: *MsgContext) !void {
47 try ctx.msgNode(try data.src.get(ctx), "{s}", .{
48 data.msg.slice(ctx.report),
49 });
50 }
51 },
52 not_comptime_known: struct {
53 src: LazyNode,
54
55 fn build(data: @This(), ctx: *MsgContext) !void {
56 try ctx.msgNode(try data.src.get(ctx), "value must be known at compile-time", .{});
57 }
58 },
59 user_assertion_basic: struct {
60 src: LazyNode,
61
62 fn build(data: @This(), ctx: *MsgContext) !void {
63 try ctx.msgNode(try data.src.get(ctx), "assertion failure", .{});
64 }
65 },
66 user_assertion_bin: struct {
67 src: LazyNode,
68 left: Value,
69 right: Value,
70
71 fn build(data: @This(), ctx: *MsgContext) !void {
72 const node = try data.src.get(ctx);
73 try ctx.msgNode(node, "assertion failure: {} {s} {}", .{
74 fmtAssertionOperand(data.left, ctx.ip()),
75 switch (ctx.ast.nodeTag(node)) {
76 .eq => "==",
77 .ne => "!=",
78 .lt => "<",
79 .le => "<=",
80 .gt => ">",
81 .ge => ">=",
82 else => unreachable,
83 },
84 fmtAssertionOperand(data.right, ctx.ip()),
85 });
86 }
87
88 fn fmtAssertionOperand(v: Value, ip: *const InternPool) std.fmt.Formatter(fmtAssertionOperandImpl) {
89 return .{ .data = .{ .value = v, .ip = ip } };
90 }
91
92 fn fmtAssertionOperandImpl(
93 data: struct { value: Value, ip: *const InternPool },
94 comptime _: []const u8,
95 _: std.fmt.FormatOptions,
96 w: anytype,
97 ) !void {
98 if (data.value.toType(data.ip)) |t| {
99 try w.print("any {}", .{t.displayName(data.ip)});
100 } else {
101 try w.print("{}", .{data.value.displayName(data.ip)});
102 }
103 }
104 },
105 cannot_be_comptime: struct {
106 src: LazyNode,
107
108 fn build(data: @This(), ctx: *MsgContext) !void {
109 try ctx.msgNode(try data.src.get(ctx), "operation cannot be performed at compile-time (TODO: make this error more specific)", .{});
110 }
111 },
112 no_main_func: struct {
113 fn build(_: @This(), ctx: *MsgContext) !void {
114 try ctx.msg(.{ .codepoint_eol = 0 }, "missing top-level function \"main\"", .{});
115 }
116 },
117 unknown_identifier: struct {
118 node: NodeIndex,
119
120 fn build(data: @This(), ctx: *MsgContext) !void {
121 try ctx.msgNode(data.node, "unknown identifier {}", .{
122 formatId(ctx.ast.basicNodeSlice(data.node)),
123 });
124 }
125 },
126 invalid_string_identifier: struct {
127 node: NodeIndex,
128
129 fn build(data: @This(), ctx: *MsgContext) !void {
130 const slice = ctx.ast.basicNodeSlice(data.node);
131 if (slice.len == 0) {
132 try ctx.msgNode(data.node, "identifier cannot be empty", .{});
133 } else {
134 // TODO: make this point at the null escape?
135 try ctx.msgNode(data.node, "identifier cannot include null bytes", .{});
136 }
137 }
138 },
139 invalid_builtin: struct {
140 node: NodeIndex,
141
142 fn build(data: @This(), ctx: *MsgContext) !void {
143 try ctx.msgToken(
144 ctx.ast.nodeToken(data.node),
145 "unknown built-in function {s}",
146 .{ctx.ast.basicNodeSlice(data.node)},
147 );
148 }
149 },
150 builtin_mismatch_args: struct {
151 node: NodeIndex,
152 args: u32,
153
154 fn build(data: @This(), ctx: *MsgContext) !void {
155 try ctx.msgNode(data.node, "built-in function expected {d} arguments, received {d}", .{
156 data.args,
157 ctx.ast.extraData(ctx.ast.nodeData(data.node).call.extra, Ast.Call).args.len,
158 });
159 }
160 },
161 duplicate_function: struct {
162 node: NodeIndex,
163 prev: NodeIndex,
164
165 fn build(data: @This(), ctx: *MsgContext) !void {
166 const id_token = ctx.ast.idToken(data.node);
167 try ctx.msgToken(id_token, "re-declaration of {}", .{
168 formatId(ctx.ast.tokenSlice(id_token)),
169 });
170 try ctx.msgToken(ctx.ast.idToken(data.prev), "also declared here", .{});
171 // TODO: be able to store a full list of other declarations
172 // and add them here as "and here" and so on
173 }
174 },
175 duplicate_param: struct {
176 node: NodeIndex,
177 prev: NodeIndex,
178
179 fn build(data: @This(), ctx: *MsgContext) !void {
180 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of {}", .{
181 formatId(ctx.ast.fullParamDecl(data.node).id),
182 });
183 try ctx.msgToken(ctx.ast.nodeToken(data.prev), "also declared here", .{});
184 // TODO: be able to store a full list of other declarations
185 // and add them here as "and here" and so on
186 }
187 },
188 invalid_return: struct {
189 node: NodeIndex,
190
191 fn build(data: @This(), ctx: *MsgContext) !void {
192 try ctx.msgNode(
193 data.node,
194 "return cannot be used outside of a function body",
195 .{},
196 );
197 }
198 },
199 missing_result_type: struct {
200 node: NodeIndex,
201
202 fn build(data: @This(), ctx: *MsgContext) !void {
203 try ctx.msgNode(
204 data.node,
205 "cannot infer struct initialization type",
206 .{},
207 );
208 }
209 },
210 unresolved_label: struct {
211 node: NodeIndex,
212
213 fn build(data: @This(), ctx: *MsgContext) !void {
214 try ctx.msg(
215 .{ .two_tokens = ctx.ast.nodeToken(data.node) + 1 },
216 "unknown block label {}",
217 .{formatId(ctx.ast.tokenSlice(ctx.ast.nodeToken(data.node) + 2))},
218 );
219 }
220 },
221 shadow_builtin_variable: struct {
222 node: NodeIndex,
223
224 fn build(data: @This(), ctx: *MsgContext) !void {
225 try ctx.msgToken(
226 ctx.ast.nodeToken(data.node) + 1,
227 "declaration shadows reserved built-in {}",
228 .{formatId(ctx.ast.fullVarDecl(data.node).id)},
229 );
230 }
231 },
232 comptime_float_to_int: struct {
233 val: f64,
234 src: LazyNode,
235
236 fn build(data: @This(), ctx: *MsgContext) !void {
237 const node = try data.src.get(ctx);
238 try ctx.msgNode(node, "numeric literal \"{d}\" cannot be coerced to \"int\"", .{data.val});
239 }
240 },
241 top_level_statement: struct {
242 src: NodeIndex,
243
244 fn build(data: @This(), ctx: *MsgContext) !void {
245 try ctx.msgNode(data.src, "top-level statements are not allowed", .{});
246 try ctx.msgAlone("to run code on application startup, add a 'fn main'", .{});
247 }
248 },
249 coercion: struct {
250 from: Type,
251 to: Type,
252 src: LazyNode,
253
254 fn build(data: @This(), ctx: *MsgContext) !void {
255 const node = try data.src.get(ctx);
256 try ctx.msgNode(node, "expected \"{s}\", got \"{s}\"", .{
257 data.to.displayName(ctx.ip()),
258 data.from.displayName(ctx.ip()),
259 });
260 }
261 },
262 call_non_function: struct {
263 call: LazyNode,
264 ty: Type,
265
266 fn build(data: @This(), ctx: *MsgContext) !void {
267 const node = try data.call.get(ctx);
268 try ctx.msgNode(node, "\"{s}\" is not callable", .{
269 data.ty.displayName(ctx.ip()),
270 });
271 }
272 },
273 call_mismatch_args: struct {
274 call: LazyNode,
275 actual: u32,
276 expected: u32,
277
278 fn build(data: @This(), ctx: *MsgContext) !void {
279 const node = try data.call.get(ctx);
280 try ctx.msgNode(node, "function expected {d} arguments, received {d}", .{
281 data.expected,
282 data.actual,
283 });
284 }
285 },
286 runtime_incompatible_type: struct {
287 src: LazyNode,
288 type: Type,
289
290 fn build(data: @This(), ctx: *MsgContext) !void {
291 const node = try data.src.get(ctx);
292 try ctx.msgNode(node, "type \"{}\" cannot be represented as a runtime value", .{
293 data.type.displayName(ctx.ip()),
294 });
295 }
296 },
297 type_no_defined_representation: struct {
298 src: LazyNode,
299 type: Type,
300
301 fn build(data: @This(), ctx: *MsgContext) !void {
302 const node = try data.src.get(ctx);
303 try ctx.msgNode(node, "type \"{}\" does not have a defined representation", .{
304 data.type.displayName(ctx.ip()),
305 });
306 try ctx.msgAlone("TODO: clarify why for the user's specific case; structs and functions are not guaranteed to hold the same field and parameter count+types. this allows future optimization passes to be smarter", .{});
307 }
308 },
309 param_comptime_only: struct {
310 src: LazyNode,
311 type: Type,
312
313 fn build(data: @This(), ctx: *MsgContext) !void {
314 const node = try data.src.get(ctx);
315 try ctx.msgNode(node, "parameter of comptime type \"{s}\" must be marked \"comptime\"", .{
316 data.type.displayName(ctx.ip()),
317 });
318 }
319 },
320 assign_builtin: struct {
321 node: NodeIndex,
322
323 fn build(data: @This(), ctx: *MsgContext) !void {
324 try ctx.msgNode(
325 data.node,
326 "cannot re-assign built-in {}",
327 .{formatId(ctx.ast.basicNodeSlice(data.node))},
328 );
329 }
330 },
331 invalid_assignment_symbol: struct {
332 node: NodeIndex,
333 decl: NodeIndex,
334 kind: UnusedKind,
335
336 fn build(data: @This(), ctx: *MsgContext) !void {
337 const id = switch (ctx.ast.nodeTag(data.decl)) {
338 .param_decl => ctx.ast.fullParamDecl(data.decl).id,
339 .fn_decl => ctx.ast.fullFnDecl(data.decl).id,
340 .var_decl => ctx.ast.fullVarDecl(data.decl).id,
341 else => unreachable, // not a valid symbol node
342 };
343
344 try ctx.msgToken(ctx.ast.idToken(data.node), "cannot assign to {s} {}", .{
345 @tagName(data.kind),
346 formatId(id),
347 });
348 try ctx.msgToken(ctx.ast.idToken(data.decl), "declared here", .{});
349 }
350 },
351 invalid_assignment_target: struct {
352 node: NodeIndex,
353
354 fn build(data: @This(), ctx: *MsgContext) !void {
355 try ctx.msgNode(data.node, "invalid assignment target", .{});
356 }
357 },
358 bin_op_mismatch: struct {
359 src: LazyNode,
360 lhs: Type,
361 rhs: Type,
362
363 fn build(data: @This(), ctx: *MsgContext) !void {
364 const node = try data.src.get(ctx);
365 try ctx.msgNode(
366 node,
367 "\"{s}\" operator is not compatible with types \"{}\" and \"{}\"",
368 .{
369 ctx.ast.tokenSlice(ctx.ast.nodeToken(node)),
370 data.lhs.displayName(ctx.ip()),
371 data.rhs.displayName(ctx.ip()),
372 },
373 );
374 }
375 },
376 un_op_mismatch: struct {
377 src: LazyNode,
378 ty: Type,
379
380 fn build(data: @This(), ctx: *MsgContext) !void {
381 const node = try data.src.get(ctx);
382 try ctx.msgNode(
383 node,
384 "\"{s}\" operator is not compatible with type \"{}\"",
385 .{
386 ctx.ast.tokenSlice(ctx.ast.nodeToken(node)),
387 data.ty.displayName(ctx.ip()),
388 },
389 );
390 }
391 },
392 dependency_loop: struct {
393 src: LazyNode,
394
395 fn build(data: @This(), ctx: *MsgContext) !void {
396 const node = try data.src.get(ctx);
397 try ctx.msgNode(
398 node,
399 "type dependency loop (TODO: explain this better, consider using less inferred return types)",
400 .{},
401 );
402 }
403 },
404 struct_init_invalid_type: struct {
405 ty: Type,
406 src: LazyNode,
407
408 fn build(data: @This(), ctx: *MsgContext) !void {
409 const node = try data.src.get(ctx);
410 try ctx.msgNode(
411 node,
412 "\"{}\" is not constructable via struct initialization syntax",
413 .{data.ty.displayName(ctx.ip())},
414 );
415 }
416 },
417 struct_init_unknown_field: struct {
418 ty: Type,
419 src: LazyNode,
420
421 fn build(data: @This(), ctx: *MsgContext) !void {
422 const node = try data.src.get(ctx);
423 try ctx.msgNode(
424 node,
425 "\"{}\" does not have field {}",
426 .{ data.ty.displayName(ctx.ip()), formatId("field") },
427 );
428 }
429 },
430 struct_init_duplicate_field: struct {
431 ty: Type,
432 src: LazyNode,
433
434 fn build(data: @This(), ctx: *MsgContext) !void {
435 const node = try data.src.get(ctx);
436 try ctx.msgNode(
437 node,
438 "duplicate field {}",
439 .{formatId("field")},
440 );
441 }
442 },
443 struct_init_missing_field: struct {
444 ty: Type,
445 src: LazyNode,
446 field: u32,
447
448 fn build(data: @This(), ctx: *MsgContext) !void {
449 const node = try data.src.get(ctx);
450 try ctx.msgNode(
451 node,
452 "{} is missing field {}",
453 .{
454 data.ty.displayName(ctx.ip()),
455 formatId(
456 data.ty.toStructType(ctx.ip()).?
457 .field_keys.at(ctx.ip(), data.field)
458 .toSlice(ctx.ip()),
459 ),
460 },
461 );
462 }
463 },
464
465 field_lookup_failed: struct {
466 ty: Type,
467 src: LazyNode,
468 field: Value,
469
470 fn build(data: @This(), ctx: *MsgContext) !void {
471 const node = try data.src.get(ctx);
472 try ctx.msgNode(
473 node,
474 "{} is not a field on \"{}\"",
475 .{
476 formatId(data.field.asString(ctx.ip()).toSlice(ctx.ip())),
477 data.ty.displayName(ctx.ip()),
478 },
479 );
480 }
481 },
482 field_invalid_type: struct {
483 ty: Type,
484 src: LazyNode,
485
486 fn build(data: @This(), ctx: *MsgContext) !void {
487 const node = try data.src.get(ctx);
488 try ctx.msgNode(
489 node,
490 "\"{}\" does not support field access",
491 .{data.ty.displayName(ctx.ip())},
492 );
493 }
494 },
495};
496
497pub const Warn = union(enum) {
498 pub const FieldEnum = std.meta.FieldEnum(Warn);
499
500 unused: struct {
501 node: NodeIndex,
502 kind: UnusedKind,
503
504 fn build(data: @This(), ctx: *MsgContext) !void {
505 const id = switch (ctx.ast.nodeTag(data.node)) {
506 .param_decl => ctx.ast.fullParamDecl(data.node).id,
507 .fn_decl => ctx.ast.fullFnDecl(data.node).id,
508 .var_decl => ctx.ast.fullVarDecl(data.node).id,
509 .struct_decl => ctx.ast.fullStructDecl(data.node).id,
510 else => unreachable, // not a valid symbol node
511 };
512
513 try ctx.msgToken(ctx.ast.idToken(data.node), "unused {s} {}", .{
514 @tagName(data.kind),
515 formatId(id),
516 });
517
518 try ctx.msgAlone("if this was intentional, discard it with \"_ = {};\"", .{formatIdCode(id)});
519 }
520 },
521 zero_prefixed_literal: struct {
522 token: TokenIndex,
523
524 fn build(data: @This(), ctx: *MsgContext) !void {
525 try ctx.msg(.{
526 .token_substring = .{
527 .token = data.token,
528 .start = 0,
529 .len = brk: {
530 const slice = ctx.ast.tokenSlice(data.token);
531 var i: u32 = 0;
532 while (slice[i] == '0') i += 1;
533 if (slice[i] == '.') i -= 1;
534 break :brk i;
535 },
536 },
537 }, "avoid redundant zeros in numbers", .{});
538 }
539 },
540 not_mutated_var: struct {
541 node: NodeIndex,
542
543 fn build(data: @This(), ctx: *MsgContext) !void {
544 const id = switch (ctx.ast.nodeTag(data.node)) {
545 .var_decl => ctx.ast.fullVarDecl(data.node).id,
546 else => unreachable, // not a valid variable symbol node
547 };
548
549 try ctx.msgToken(ctx.ast.idToken(data.node), "variable {} not mutated", .{
550 formatId(id),
551 });
552 }
553 },
554 invalid_discard: struct {
555 node: NodeIndex,
556
557 fn build(data: @This(), ctx: *MsgContext) !void {
558 try ctx.msgToken(ctx.ast.nodeToken(data.node), "\"let\" is not required for a discard", .{});
559 }
560 },
561 discard_builtin: struct {
562 node: NodeIndex,
563
564 fn build(data: @This(), ctx: *MsgContext) !void {
565 try ctx.msgToken(ctx.ast.nodeToken(data.node), "discarding a built-in symbol is meaningless", .{});
566 }
567 },
568 unnecessary_discard: struct {
569 node: NodeIndex,
570
571 fn build(data: @This(), ctx: *MsgContext) !void {
572 try ctx.msgToken(ctx.ast.nodeToken(data.node), "useless discard", .{});
573 }
574 },
575 dangling_identifier: struct {
576 node: NodeIndex,
577
578 fn build(data: @This(), ctx: *MsgContext) !void {
579 try ctx.msgToken(ctx.ast.nodeToken(data.node), "dangling identifier does nothing", .{});
580 }
581 },
582 numeric_int_var_ambiguity: struct {
583 src: LazyNode,
584
585 fn build(data: @This(), ctx: *MsgContext) !void {
586 const node = try data.src.get(ctx);
587 try ctx.msgNode(node, "constant numeric values at runtime needs a result type, implicitly casting to \"float\"", .{});
588 // TODO: trace to the result type and point at it
589 try ctx.msgAlone("add \": float\" or \": int\" after the variable name to set the type", .{});
590 }
591 },
592 numeric_int_array_var_ambiguity: struct {
593 src: LazyNode,
594
595 fn build(data: @This(), ctx: *MsgContext) !void {
596 const node = try data.src.get(ctx);
597 try ctx.msgNode(
598 node,
599 "\"{}\" cannot be represented as a runtime value, implicitly casting to \"[]float\"",
600 .{
601 Type.numeric_literal_array.displayName(ctx.ip()),
602 },
603 );
604 try ctx.msgAlone("add \": []float\" or \": []int\" after the variable name to set the type", .{});
605 }
606 },
607};
608
609pub const DumpContext = union(enum) {
610 /// Reports generated from Semantic Analysis require full
611 /// compilation state, notably InternPool to read values.
612 comp: struct {
613 comp: *Comp,
614 file: *Comp.File,
615 },
616 /// Reports generated from Parse and Transform are
617 /// single-file, and can be dumped without full state.
618 single_file: struct {
619 allocator: Allocator,
620 url: []const u8,
621 file: union(enum) {
622 utf8_source: [:0]const u8,
623 ast: *const Ast,
624 },
625 },
626
627 pub fn initComp(comp: *Comp, file: *Comp.File) DumpContext {
628 return .{ .comp = .{ .comp = comp, .file = file } };
629 }
630};
631
632pub const UnusedKind = enum {
633 param,
634 constant,
635 variable,
636 declaration,
637};
638
639pub const String = struct {
640 start: u32,
641 len: u32,
642
643 pub fn slice(str: String, report: *const Report) []const u8 {
644 return report.string_bytes[str.start..][0..str.len];
645 }
646};
647
648const Prefix = enum {
649 @"error",
650 warn,
651 note,
652
653 fn color(prefix: Prefix) std.io.tty.Color {
654 return switch (prefix) {
655 .@"error" => .red,
656 .warn => .yellow,
657 .note => .blue,
658 };
659 }
660
661 fn symbol(prefix: Prefix, ascii: bool) []const u8 {
662 return switch (prefix) {
663 .@"error" => if (ascii) "X" else "✘",
664 .warn => if (ascii) "!" else "▲",
665 .note => "",
666 };
667 }
668};
669
670pub fn deinit(report: Report, gpa: Allocator) void {
671 gpa.free(report.errors);
672 gpa.free(report.warnings);
673 gpa.free(report.string_bytes);
674}
675
676pub fn dump(report: Report, ctx: DumpContext, out: core.OutStream) !void {
677 var arena = std.heap.ArenaAllocator.init(switch (ctx) {
678 .comp => |comp_ctx| comp_ctx.comp.gpa,
679 .single_file => |single_file_ctx| single_file_ctx.allocator,
680 });
681 defer arena.deinit();
682
683 var fake_ast_data: Ast = undefined;
684
685 const ast: *const Ast = switch (ctx) {
686 .comp => |comp_ctx|
687 // Ast cannot be queried if the error is in computing the Ast
688 if (report.errors.len > 0 and report.errors[0].data == .ast) fake_ast: {
689 fake_ast_data = .{
690 .utf8_source = (comp_ctx.file.getSource(comp_ctx.comp.gpa) catch {
691 @panic("TODO");
692 // try report.dumpErrorDuringDump(report, .get_source, err, @errorReturnTrace()),
693 // return;
694 }).code,
695 .tokens = (Tokenizer.Token.List{}).slice(),
696 .nodes = (Ast.Node.List{}).slice(),
697 .extra = &.{},
698 };
699 break :fake_ast &fake_ast_data;
700 } else comp_ctx.file.getAst(comp_ctx.comp) catch {
701 @panic("TODO");
702 // try report.dumpErrorDuringDump(report, .get_ast, err, @errorReturnTrace()),
703 // return;
704 },
705 .single_file => |single_file_ctx| switch (single_file_ctx.file) {
706 .ast => |ast| ast,
707 .utf8_source => |source| fake_ast: {
708 fake_ast_data = .{
709 .utf8_source = source,
710 .tokens = (Tokenizer.Token.List{}).slice(),
711 .nodes = (Ast.Node.List{}).slice(),
712 .extra = &.{},
713 };
714 break :fake_ast &fake_ast_data;
715 },
716 },
717 };
718
719 const url = switch (ctx) {
720 .comp => |comp_ctx| comp_ctx.file.file_path,
721 .single_file => |file_ctx| file_ctx.url,
722 };
723
724 const msg_ctx: MsgContext = .{
725 .report = &report,
726 .comp = switch (ctx) {
727 .comp => |comp_ctx| comp_ctx.comp,
728 else => null,
729 },
730 .file = switch (ctx) {
731 .comp => |comp_ctx| comp_ctx.file,
732 else => null,
733 },
734 .ast = ast,
735 .utf8_source = ast.utf8_source,
736
737 .arena = undefined,
738 .components = undefined,
739 };
740
741 for (report.errors) |item| {
742 try formatItem(Err, item, arena.allocator(), url, msg_ctx, out);
743 _ = arena.reset(.retain_capacity);
744 }
745 for (report.warnings) |item| {
746 try formatItem(Warn, item, arena.allocator(), url, msg_ctx, out);
747 _ = arena.reset(.retain_capacity);
748 }
749}
750
751fn formatItem(
752 comptime T: type,
753 item: Item(T),
754 arena: Allocator,
755 url: []const u8,
756 ctx_init: MsgContext,
757 out: core.OutStream,
758) !void {
759 var sfa = std.heap.stackFallback(16384, arena);
760 const alloc = sfa.get();
761
762 var ctx: MsgContext = ctx_init;
763 ctx.arena = alloc;
764 ctx.components = ArrayListUnmanaged(Component).initCapacity(alloc, 16) catch
765 unreachable; // stack-fallback allocator has enough space.
766
767 switch (item.data) {
768 inline else => |payload| try payload.build(&ctx),
769 }
770
771 assert(ctx.components.items.len > 0);
772
773 const writer = out.writer;
774 const tty = out.tty;
775
776 const width = brk: {
777 var max: usize = 0;
778 for (ctx.components.items) |component| {
779 if (component.location) |loc| {
780 max = @max(loc.loc.line.oneBased(), max);
781 }
782 }
783
784 break :brk if (max > 0) std.fmt.count("{d}", .{max}) else 0;
785 };
786
787 for (ctx.components.items, 1..) |component, i| {
788 const prefix: Prefix = if (i == 1)
789 switch (T) {
790 Err => .@"error",
791 Warn => .warn,
792 else => @compileError(unreachable),
793 }
794 else
795 .note;
796
797 const is_last = i == ctx.components.items.len;
798
799 try writer.writeByteNTimes(' ', width);
800
801 if (i != 1) {
802 try tty.setColor(writer, .dim);
803 try writer.writeAll("│");
804 try tty.setColor(writer, .reset);
805 }
806
807 try tty.setColor(writer, prefix.color());
808 try tty.setColor(writer, .bold);
809 try writer.writeAll(prefix.symbol(false));
810 try writer.writeAll(" ");
811 try writer.writeAll(@tagName(prefix));
812 try tty.setColor(writer, .reset);
813 try tty.setColor(writer, .dim);
814 try writer.writeAll(": ");
815 try tty.setColor(writer, .reset);
816 if (i == 1)
817 try tty.setColor(writer, .bold);
818 try writer.writeAll(component.message_utf8);
819 if (i == 1)
820 try tty.setColor(writer, .reset);
821 try writer.writeAll("\n");
822
823 const utf8_source = ctx.utf8_source;
824
825 const at = component.location orelse {
826 if (is_last) {
827 try tty.setColor(writer, .dim);
828 try writer.writeByteNTimes(' ', width);
829 try writer.print("{s}", .{"┴"});
830 try writer.writeAll("\n");
831 try tty.setColor(writer, .reset);
832 }
833 continue;
834 };
835
836 const line_before = utf8_source[at.loc.line_start..at.byte];
837 const trimmed_before = std.mem.trimLeft(u8, line_before, " \t\r");
838 // The text after the highlighted component can be out of bounds if the
839 // highlighted token is EOF, which is hardcoded to " " to highlight
840 // empty space after the line.
841 const line_after = if (at.byte + at.slice.len < at.loc.line_end)
842 utf8_source[at.byte + at.slice.len .. at.loc.line_end]
843 else
844 "";
845
846 try tty.setColor(writer, .dim);
847 try writer.writeByteNTimes(' ', width);
848 try writer.print("{s}{s}:{d}:{d}\n", .{
849 if (i == 1) "┬ " else "├ ",
850 url,
851 at.loc.line.oneBased(),
852 at.loc.column.oneBased(),
853 });
854 try writer.print("{d}", .{at.loc.line.oneBased()});
855 try writer.print("{s} ", .{"│"});
856 try tty.setColor(writer, .reset);
857 try writer.writeAll(trimmed_before);
858 try tty.setColor(writer, prefix.color());
859 try tty.setColor(writer, .bold);
860 try writer.writeAll(at.slice);
861 try tty.setColor(writer, .reset);
862 try writer.writeAll(line_after);
863 try writer.writeAll("\n");
864 try tty.setColor(writer, .dim);
865 try writer.writeByteNTimes(' ', width);
866 try writer.writeAll(if (is_last) "╰" else "├");
867 try writer.writeBytesNTimes(
868 "─",
869 1 + (at.byte - at.loc.line_start) -
870 (line_before.len - trimmed_before.len),
871 );
872 try tty.setColor(writer, .reset);
873 try tty.setColor(writer, prefix.color());
874 try tty.setColor(writer, .bold);
875 try writer.writeBytesNTimes("━", @max(at.slice.len, 1));
876 try tty.setColor(writer, .reset);
877 try writer.writeAll("\n");
878 }
879
880 if (builtin.mode == .Debug and debug_show_report_trace) {
881 var trace = item.debug_stack_trace;
882 std.debug.dumpStackTrace(trace.trace());
883 }
884}
885
886// pub fn dumpToStdErr(report: Report, comp: *Comp) !void {
887// const stderr = std.io.getStdErr();
888// const tty = std.io.tty.detectConfig(stderr);
889
890// if (false or
891// // Windows API uses its own API calls, so a buffered writer
892// // cannot be used :/
893// (builtin.os.tag == .windows and tty == .windows_api) or
894// // Debug stack trace printer writes directly to standard err,
895// // so we need to be unbuffered for that.
896// (builtin.mode == .Debug and debug_show_report_trace))
897// {
898// try dumpToWriter(report, comp, stderr.writer(), tty);
899// } else {
900// var buf = std.io.bufferedWriter(stderr.writer());
901// const writer = buf.writer();
902// try dumpToWriter(report, comp, writer, tty);
903// try buf.flush();
904// }
905// }
906
907pub const Builder = struct {
908 gpa: Allocator,
909 errors: ArrayListUnmanaged(Item(Err)),
910 warnings: ArrayListUnmanaged(Item(Warn)),
911 string_bytes: ArrayListUnmanaged(u8),
912
913 pub fn init(gpa: Allocator) Builder {
914 return .{
915 .gpa = gpa,
916 .errors = .empty,
917 .warnings = .empty,
918 .string_bytes = .empty,
919 };
920 }
921
922 pub fn err(b: *Builder, comptime kind: Err.FieldEnum, data: core.FieldType(Err, kind)) !void {
923 try errAt(b, kind, data, @returnAddress());
924 }
925
926 pub fn warn(b: *Builder, comptime kind: Warn.FieldEnum, data: core.FieldType(Warn, kind)) !void {
927 try warnAt(b, kind, data, @returnAddress());
928 }
929
930 pub fn errAt(
931 b: *Builder,
932 comptime kind: Err.FieldEnum,
933 data: core.FieldType(Err, kind),
934 return_address: usize,
935 ) !void {
936 try b.errors.append(b.gpa, .{
937 .data = @unionInit(Err, @tagName(kind), data),
938 .debug_stack_trace = DebugStoredTrace.capture(return_address),
939 });
940 }
941
942 pub fn warnAt(
943 b: *Builder,
944 comptime kind: Warn.FieldEnum,
945 data: core.FieldType(Warn, kind),
946 return_address: usize,
947 ) !void {
948 try b.warnings.append(b.gpa, .{
949 .data = @unionInit(Warn, @tagName(kind), data),
950 .debug_stack_trace = DebugStoredTrace.capture(return_address),
951 });
952 }
953
954 pub fn addString(b: *Builder, str: []const u8) !String {
955 const top = b.string_bytes.items.len;
956 try b.string_bytes.appendSlice(b.gpa, str);
957 return .{ .start = @intCast(top), .len = @intCast(str.len) };
958 }
959
960 pub fn currentFileString(b: *Builder) !String {
961 return b.current_file_str orelse {
962 b.current_file_str = try b.addString(b.current_file.file_path);
963 return b.current_file_str.?;
964 };
965 }
966
967 pub fn deinit(b: *Builder) void {
968 b.errors.deinit(b.gpa);
969 b.warnings.deinit(b.gpa);
970 b.string_bytes.deinit(b.gpa);
971 }
972
973 pub fn finalize(b: *Builder) !Report {
974 const errors = try b.errors.toOwnedSlice(b.gpa);
975 errdefer b.errors = .fromOwnedSlice(errors);
976
977 const warnings = try b.warnings.toOwnedSlice(b.gpa);
978 errdefer b.warnings = .fromOwnedSlice(warnings);
979
980 const string_bytes = try b.string_bytes.toOwnedSlice(b.gpa);
981 errdefer b.string_bytes = .fromOwnedSlice(string_bytes);
982
983 return .{
984 .errors = errors,
985 .warnings = warnings,
986 .string_bytes = string_bytes,
987 };
988 }
989};
990
991pub const SliceLoc = struct {
992 loc: Location,
993 byte: ByteOffset,
994 slice: []const u8,
995};
996
997const Component = struct {
998 message_utf8: []const u8,
999 location: ?SliceLoc,
1000};
1001
1002pub const Highlight = union(enum) {
1003 /// No source view
1004 alone,
1005 /// Highlight one token
1006 token: TokenIndex,
1007 /// Highlight one token without referring to the Ast
1008 token_stored: Tokenizer.Token,
1009 /// Highlight two tokens
1010 two_tokens: TokenIndex,
1011 /// Highlight a substring of a token
1012 token_substring: struct { token: TokenIndex, start: ByteOffset, len: u32 },
1013 /// Highlight a Node
1014 node: NodeIndex,
1015 /// Highlight a codepoint
1016 codepoint: ByteOffset,
1017 /// Highlight from the start of a codepoint to '\n'
1018 codepoint_eol: ByteOffset,
1019
1020 pub fn loc(highlight: Highlight, ast: *const Ast) ?SliceLoc {
1021 return switch (highlight) {
1022 .alone => null,
1023 inline .node,
1024 .token,
1025 .token_substring,
1026 .two_tokens,
1027 => |payload, tag| loc: {
1028 const token_index: TokenIndex = switch (tag) {
1029 .node => ast.nodeToken(payload),
1030 .token, .two_tokens => payload,
1031 .token_substring => payload.token,
1032 else => @compileError(unreachable),
1033 };
1034
1035 const token = ast.tokens.get(token_index);
1036
1037 var location = ast.tokenLocation(0, token_index);
1038 var slice = token.slice(ast.utf8_source);
1039 var byte = token.offset;
1040
1041 if (tag == .token_substring) {
1042 location.column = location.column.add(payload.start);
1043 slice.ptr += payload.start;
1044 slice.len = payload.len;
1045 byte += payload.start;
1046 }
1047
1048 break :loc .{ .loc = location, .slice = slice, .byte = byte };
1049 },
1050 .token_stored => |token| loc: {
1051 const location = Tokenizer.tokenLocation(ast.utf8_source, 0, token.offset);
1052
1053 break :loc .{
1054 .loc = location,
1055 .slice = if (token.tag != .eof)
1056 token.slice(ast.utf8_source)
1057 else
1058 " ",
1059 .byte = token.offset,
1060 };
1061 },
1062 inline .codepoint,
1063 .codepoint_eol,
1064 => |b, tag| loc: {
1065 const len = switch (tag) {
1066 .codepoint => std.unicode.utf8ByteSequenceLength(ast.utf8_source[b]) catch 1,
1067 .codepoint_eol => if (std.mem.indexOfScalar(u8, ast.utf8_source[b..], '\n')) |n|
1068 n
1069 else
1070 (ast.utf8_source.len - b),
1071 else => @compileError(unreachable),
1072 };
1073 break :loc .{
1074 .byte = b,
1075 .slice = ast.utf8_source[b..][0..len],
1076 .loc = .{
1077 .column = .start,
1078 .line = .start,
1079 .line_start = b,
1080 .line_end = @intCast(b + len),
1081 },
1082 };
1083 },
1084 };
1085 }
1086};
1087
1088/// This serves as a small API to make creating messages easier.
1089pub const MsgContext = struct {
1090 file: ?*const File,
1091 comp: ?*const Comp,
1092 ast: *const Ast,
1093 report: *const Report,
1094 utf8_source: [:0]const u8,
1095
1096 /// This arena is a stack fallback with 16kb, then
1097 /// falling back to an arena allocator. This should
1098 /// make the cost of arbitrarily creating `Component`
1099 /// nodes extremely lightweight.
1100 arena: Allocator,
1101
1102 /// First component is "error" or "warn"
1103 /// Trailing components are "note"
1104 components: ArrayListUnmanaged(Component),
1105
1106 pub fn msg(m: *MsgContext, highlight: Highlight, comptime fmt: []const u8, args: anytype) !void {
1107 comptime if (!std.mem.startsWith(u8, fmt, "TODO")) switch (fmt[0]) {
1108 'A'...'Z' => @compileError("template invalid: " ++ fmt), // style guide: lowercase
1109 else => {},
1110 };
1111 var arr: std.ArrayListUnmanaged(u8) = .{};
1112 try std.fmt.format(arr.writer(m.arena), fmt, args);
1113 try m.components.append(m.arena, .{
1114 .message_utf8 = try arr.toOwnedSlice(m.arena),
1115 .location = highlight.loc(m.ast),
1116 });
1117 }
1118
1119 pub fn msgNode(m: *MsgContext, node: NodeIndex, comptime fmt: []const u8, args: anytype) !void {
1120 return m.msg(.{ .node = node }, fmt, args);
1121 }
1122
1123 pub fn msgToken(m: *MsgContext, token: TokenIndex, comptime fmt: []const u8, args: anytype) !void {
1124 return m.msg(.{ .token = token }, fmt, args);
1125 }
1126
1127 pub fn msgAlone(m: *MsgContext, comptime fmt: []const u8, args: anytype) !void {
1128 return m.msg(.alone, fmt, args);
1129 }
1130
1131 pub fn ip(ctx: *const MsgContext) *const InternPool {
1132 return &ctx.comp.?.intern_pool;
1133 }
1134};
1135
1136pub const LazyNode = struct {
1137 // TODO: remove this, it should be inferable off of the analysis unit that belongs to the report
1138 decl: Comp.Decl.Index,
1139 inst: Hir.Inst.Index,
1140 operand: Operand,
1141
1142 pub fn get(lazy: LazyNode, ctx: *MsgContext) !NodeIndex {
1143 const decl = ctx.comp.?.intern_pool.declPtrConst(lazy.decl);
1144 const ns = ctx.comp.?.intern_pool.namespacePtrConst(decl.src_namespace);
1145
1146 // TODO: This might change if we have errors like
1147 // - function arguments not matching at <callsite>
1148 // - function declared <definition>
1149 // And they are both in different files.
1150 // To resolve this, `Component` needs to store which file it is in.
1151 assert(ns.file_scope == ctx.file);
1152
1153 if (ns.parent.unwrap() != null) {
1154 @panic("TODO: LazyNode.get when the decl is in a nested namespace");
1155 }
1156
1157 const hir = &(ctx.file.?.hir orelse
1158 // this will be required once incremental compilation hits
1159 @panic("TODO: hir was unloaded"));
1160
1161 const decl_inst = decl.teg_decl_index;
1162
1163 const teg_decl = hir.instData(.declaration, decl_inst.resolve(ctx.ip()));
1164
1165 const node_offset = hir.instNodeOffset(lazy.inst);
1166
1167 const base_node: NodeIndex = @intCast(teg_decl.src_node + node_offset);
1168 if (lazy.operand == .main_node)
1169 return base_node;
1170
1171 const data = ctx.ast.nodeData(base_node);
1172
1173 const node_tag = ctx.ast.nodeTag(base_node);
1174
1175 switch (hir.instTag(lazy.inst)) {
1176 .coercion => switch (node_tag) {
1177 .var_decl => switch (lazy.operand) {
1178 .lhs => return data.var_decl.type.unwrap().?,
1179 .rhs => return data.var_decl.init,
1180 else => {},
1181 },
1182 else => {},
1183 },
1184 .call => switch (node_tag) {
1185 .call => switch (lazy.operand) {
1186 .operand => return data.call.base,
1187 else => |op| return ctx.ast.extraData(data.call.extra, Ast.Call)
1188 .args.slice(ctx.ast)[op.getArgId()],
1189 },
1190 else => {},
1191 },
1192 .@"break" => switch (node_tag) {
1193 .@"return" => return data.@"return".expr.unwrap().?,
1194 .@"break" => return data.@"break".value.unwrap().?,
1195 .block_implicit_return => {
1196 const statements = ctx.ast.nodeStatements(base_node);
1197 return statements[statements.len - 1];
1198 },
1199 else => return base_node,
1200 },
1201 .memory => switch (node_tag) {
1202 .var_decl => switch (lazy.operand) {
1203 .operand => return data.var_decl.init,
1204 else => {},
1205 },
1206 else => {},
1207 },
1208 .store => {
1209 assert(node_tag.isAssignBin());
1210 switch (lazy.operand) {
1211 .lhs => return data.bin.lhs,
1212 .rhs => return data.bin.rhs,
1213 else => unreachable,
1214 }
1215 },
1216 .declaration => {
1217 assert(node_tag == .fn_decl);
1218 const fn_decl = ctx.ast.extraData(data.function.extra, Ast.Fn);
1219 return switch (lazy.operand) {
1220 .return_type => fn_decl.return_type.unwrap().?,
1221 else => fn_decl.params.slice(ctx.ast)[lazy.operand.getArgId()],
1222 };
1223 },
1224 .@"extern" => {
1225 return base_node;
1226 },
1227 .struct_init => {
1228 assert(node_tag == .struct_init);
1229 switch (lazy.operand) {
1230 .operand => return data.struct_init.base.unwrap() orelse
1231 // for anon struct initializations, this will point at the '.'
1232 base_node,
1233 else => {
1234 const fields = ctx.ast
1235 .extraData(data.struct_init.extra, Ast.SubList)
1236 .slice(ctx.ast);
1237 const field = fields[lazy.operand.getArgId()];
1238 return ctx.ast.nodeData(field).un.expr;
1239 },
1240 }
1241 },
1242 .struct_decl => {
1243 const fields = data.list.slice(ctx.ast);
1244 const field = fields[lazy.operand.getArgId()];
1245 return ctx.ast.nodeData(field).un.expr;
1246 },
1247 .array_init => {
1248 assert(node_tag == .array_init);
1249 const items = data.list.slice(ctx.ast);
1250 return items[lazy.operand.getArgId()];
1251 },
1252 .field => {
1253 return base_node;
1254 },
1255 .@"if" => {
1256 assert(node_tag == .@"if");
1257 switch (lazy.operand) {
1258 .operand => return data.@"if".cond,
1259 .lhs => return ctx.ast.extraData(data.@"if".extra, Ast.If).yes,
1260 .rhs => return ctx.ast.extraData(data.@"if".extra, Ast.If).no.unwrap().?,
1261 else => {},
1262 }
1263 },
1264 .assert => {
1265 assert(node_tag == .assert);
1266 return data.un.expr;
1267 },
1268 else => {},
1269 }
1270
1271 std.debug.print("TODO: operand lookup: .{s} + .{s} + {}\n", .{
1272 @tagName(hir.instTag(lazy.inst)),
1273 @tagName(ctx.ast.nodeTag(base_node)),
1274 lazy.operand,
1275 });
1276
1277 return base_node;
1278 }
1279};
1280
1281pub const Operand = enum(u32) {
1282 /// Point to the main node of an instruction
1283 main_node = 0,
1284 /// Base of a call, field, or operand to a unary instruction
1285 operand = 1,
1286 /// Return type of a function declaration or lambda node
1287 return_type = 2,
1288 /// Left hand side of a binary instruction
1289 lhs = 4,
1290 /// Right hand side of a binary instruction
1291 rhs = 5,
1292 _,
1293
1294 /// One of:
1295 /// - An argument of a .call node
1296 /// - A parameter of a .fn_decl node
1297 /// - A value of a .struct_decl node
1298 pub fn argument(id: u32) Operand {
1299 return @enumFromInt(id + 6);
1300 }
1301
1302 fn getArgId(op: Operand) u32 {
1303 return @intFromEnum(op) - 6;
1304 }
1305
1306 pub fn format(op: Operand, comptime fmt: []const u8, _: std.fmt.FormatOptions, writer: std.io.AnyWriter) !void {
1307 comptime assert(fmt.len == 0);
1308 if (@intFromEnum(op) <= @intFromEnum(Operand.rhs)) {
1309 try writer.writeAll(".");
1310 try writer.writeAll(@tagName(op));
1311 } else {
1312 try writer.print(".argument({d})", .{op.getArgId()});
1313 }
1314 }
1315};
1316
1317/// Non-allocating formatter for taking an id token slice (raw), normalizing it
1318/// into it's canonical form.
1319pub fn formatId(id: []const u8) std.fmt.Formatter(formatIdImpl) {
1320 return .{ .data = id };
1321}
1322
1323fn formatIdImpl(
1324 id: []const u8,
1325 comptime format: []const u8,
1326 _: std.fmt.FormatOptions,
1327 writer: anytype,
1328) !void {
1329 comptime assert(format.len == 0);
1330
1331 if (id[0] != '@' and !Tokenizer.Token.keywords.has(id)) {
1332 // no work needed
1333 try writer.writeByte('"');
1334 try writer.writeAll(id);
1335 try writer.writeByte('"');
1336
1337 return;
1338 }
1339
1340 // determine if the entire ID can be printed without escpaing
1341 const inner = id[2 .. id.len - 1];
1342 const needs_encode = brk: {
1343 var i: usize = 0;
1344 while (i < inner.len) {
1345 switch (inner[i]) {
1346 'a'...'z', 'A'...'Z', '0'...'9', '_' => {
1347 i += 1;
1348 continue;
1349 },
1350 else => break :brk true,
1351 0x80...0xFF => |first_byte| {
1352 const len = std.unicode.utf8ByteSequenceLength(first_byte) catch
1353 unreachable; // Invalid UTF-8
1354 const codepoint = std.unicode.utf8Decode(inner[i..][0..len]) catch
1355 unreachable; // Invalid UTF-8
1356 if (!unicode_props.isIdContinue(codepoint))
1357 break :brk true;
1358 },
1359 }
1360 }
1361 break :brk false;
1362 };
1363
1364 if (needs_encode) {
1365 try writer.writeAll("@\"");
1366 } else {
1367 try writer.writeByte('"');
1368 }
1369
1370 // TODO: these two are "reasonable upper bounds" and not scientifically proven
1371 var decode_buf: [4096 * 2]u8 = undefined;
1372 //var encode_buf: [4096 * 3]u8 = undefined;
1373
1374 var it = std.mem.window(u8, inner, 4096, 4096);
1375 while (it.next()) |chunk| {
1376 const decoded = Tokenizer.decodeStringBuf(chunk, &decode_buf);
1377 if (needs_encode) {
1378 // TODO: This code is kind of duplicated, but there are small
1379 // differences in the string formatters. Ideally these are merged
1380 // under a common function that takes an argument of the 'mode' to
1381 // print the string in.
1382 const JSLink = @import("mod.zig").Js.Link;
1383 const indexOfNeedsEscape = JSLink.indexOfNeedsEscape;
1384 const canPrintWithoutEscape = JSLink.canPrintWithoutEscape;
1385
1386 var text = decoded;
1387 inner: while (text.len > 0) {
1388 const width = std.unicode.utf8ByteSequenceLength(text[0]) catch
1389 unreachable; // string is encoded incorrectly
1390
1391 const c: u21 = std.unicode.wtf8Decode(text[0..width]) catch
1392 unreachable; // string is encoded incorrectly
1393
1394 if (canPrintWithoutEscape(c)) {
1395 if (indexOfNeedsEscape(text)) |j| {
1396 try writer.writeAll(text[0..j]);
1397 text = text[j..];
1398 continue :inner;
1399 } else {
1400 try writer.writeAll(text);
1401 break :inner;
1402 }
1403 }
1404
1405 text = text[width..];
1406
1407 switch (c) {
1408 '\n' => {
1409 try writer.writeAll("\\n");
1410 },
1411 std.ascii.control_code.cr => {
1412 try writer.writeAll("\\r");
1413 },
1414 '\\' => {
1415 try writer.writeAll("\\\\");
1416 },
1417 '"' => {
1418 try writer.writeAll("\\\"");
1419 },
1420 '\t' => {
1421 try writer.writeAll("\\t");
1422 },
1423 else => {
1424 const hex_chars = "0123456789ABCDEF";
1425
1426 if (c < 0xFF) {
1427 try writer.writeAll(&[_]u8{
1428 '\\',
1429 'x',
1430 hex_chars[(c >> 4) & 0xF],
1431 hex_chars[c & 0xF],
1432 });
1433 } else if (c < 0xFFFF) {
1434 try writer.writeAll(&[_]u8{
1435 '\\',
1436 'u',
1437 '{',
1438 '0',
1439 'x',
1440 hex_chars[(c >> 12) & 0xF],
1441 hex_chars[(c >> 8) & 0xF],
1442 hex_chars[(c >> 4) & 0xF],
1443 hex_chars[c & 0xF],
1444 '}',
1445 });
1446 } else {
1447 const first_high_surrogate = 0xD800;
1448 const first_low_surrogate = 0xDC00;
1449
1450 const k = c - 0x10000;
1451 const lo = @as(usize, @intCast(first_high_surrogate + ((k >> 10) & 0x3FF)));
1452 const hi = @as(usize, @intCast(first_low_surrogate + (k & 0x3FF)));
1453
1454 try writer.writeAll(&[_]u8{
1455 '\\',
1456 'u',
1457 '{',
1458 hex_chars[lo >> 12],
1459 hex_chars[(lo >> 8) & 15],
1460 hex_chars[(lo >> 4) & 15],
1461 hex_chars[lo & 15],
1462 '}',
1463 '\\',
1464 'u',
1465 '{',
1466 '0',
1467 'x',
1468 hex_chars[hi >> 12],
1469 hex_chars[(hi >> 8) & 15],
1470 hex_chars[(hi >> 4) & 15],
1471 hex_chars[hi & 15],
1472 '}',
1473 });
1474 }
1475 },
1476 }
1477 }
1478 } else {
1479 try writer.writeAll(decoded);
1480 }
1481 }
1482
1483 try writer.writeByte('"');
1484}
1485
1486pub fn formatIdCode(id: []const u8) std.fmt.Formatter(formatIdCodeImpl) {
1487 return .{ .data = id };
1488}
1489
1490fn formatIdCodeImpl(
1491 id: []const u8,
1492 comptime format: []const u8,
1493 opts: std.fmt.FormatOptions,
1494 writer: anytype,
1495) !void {
1496 comptime assert(format.len == 0);
1497 if (id[0] != '@') {
1498 // no decoding needed
1499 try writer.writeAll(id);
1500 return;
1501 }
1502 try formatIdImpl(id, format, opts, writer);
1503}
1504
1505const Report = @This();
1506const std = @import("std");
1507const ArrayListUnmanaged = std.ArrayListUnmanaged;
1508const Allocator = std.mem.Allocator;
1509const assert = std.debug.assert;
1510
1511const builtin = @import("builtin");
1512
1513const unicode_props = @import("unicode_props.zig");
1514
1515const core = @import("mod.zig");
1516const DebugStoredTrace = core.DebugStoredTrace;
1517
1518const Tokenizer = core.Tokenizer;
1519const ByteOffset = Tokenizer.ByteOffset;
1520
1521const Ast = core.Ast;
1522const TokenIndex = Ast.TokenIndex;
1523const NodeIndex = Ast.NodeIndex;
1524const Location = Ast.Location;
1525
1526const Hir = core.Hir;
1527
1528const Comp = core.Comp;
1529const File = Comp.File;
1530
1531const InternPool = core.InternPool;
1532const Value = InternPool.Value;
1533const Type = InternPool.Type;