about summary refs log tree commit diff
path: root/boot/lower.c
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-05-24 18:18:23 +0200
committerMel <mel@rnrd.eu>2026-05-24 18:18:23 +0200
commita76fd9b5af153773273121c7aec2a9cbeed1dfb7 (patch)
treea6c619e44df4f38f60c457b9122e207b0a3c09f0 /boot/lower.c
parent8c4c219c945dfb41415e04654f488f7880e58ca2 (diff)
downloadcatskill-a76fd9b5af153773273121c7aec2a9cbeed1dfb7.tar.zst
catskill-a76fd9b5af153773273121c7aec2a9cbeed1dfb7.zip
Make diagnostics generic over passes, condensing different error handling
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'boot/lower.c')
-rw-r--r--boot/lower.c378
1 files changed, 263 insertions, 115 deletions
diff --git a/boot/lower.c b/boot/lower.c
index 01762a5..65eb18c 100644
--- a/boot/lower.c
+++ b/boot/lower.c
@@ -73,10 +73,6 @@ struct Lower_Context
     // the translation unit being created
     struct Unit* unit;
 
-    // text source of the unit in translation
-    // used for attaching diagnostics to correct lines
-    struct Source_File source;
-
     // monotonic counter of synthesized types
     uint synthetic_type_counter;
 
@@ -89,21 +85,89 @@ struct Lower_Context
 };
 
 void
-lower_emit_error(struct Unit* unit, struct Span span, struct String message)
+lower_push_error(struct Unit* unit, struct Lower_Error err)
 {
-    struct Diagnostic d = {
-        .severity = DIAGNOSTIC_ERROR,
-        .span = span,
-        .message = message,
-    };
-    array_push(&unit->diagnostics, &d);
+    array_push(&unit->lower_errors, &err);
     unit->had_error = true;
 }
 
+// render the cycle's chain to a nice human-y message.
 void
-lower_emit_error_c(struct Unit* unit, struct Span span, const ascii* message)
+lower_format_cycle_chain(
+    struct String_Buffer* buf, struct Unit* unit, struct Source_File source, Array(Type_Id) * chain)
+{
+    uint n = array_length(chain);
+    for (uint k = 0; k < n; ++k) {
+        Type_Id current_id = *array_at(Type_Id, chain, k);
+        Type_Id next_id = *array_at(Type_Id, chain, (k + 1) % n);
+        struct Type* current = *array_at(struct Type*, &unit->types.entries, current_id);
+        struct Type* next = *array_at(struct Type*, &unit->types.entries, next_id);
+        uint line, column;
+        source_position_from_span(source.source, current->span, &line, &column);
+
+        if (k > 0) string_buffer_appendf(buf, "\n");
+        string_buffer_appendf(
+            buf, "  %S (line %lu) depends on %S", STR(current->name), line, STR(next->name));
+    }
+}
+
+// turn a structured lower error into a displayable diagnostic.
+struct Diagnostic
+lower_error_to_diagnostic(struct Lower_Error* err, struct Unit* unit, struct Source_File source)
 {
-    lower_emit_error(unit, span, string_from_c_string(message));
+    struct Diagnostic d = { .severity = DIAGNOSTIC_ERROR, .span = err->span };
+    switch (err->kind) {
+    case LOWER_ERROR_UNDEFINED_TYPE:
+        d.message = string_format("undefined type '%S'", STR(err->name));
+        return d;
+    case LOWER_ERROR_DUPLICATE_TYPE:
+        d.message = string_format("duplicate type '%S'", STR(err->name));
+        return d;
+    case LOWER_ERROR_DUPLICATE_FUNCTION:
+        d.message = string_format("duplicate function '%S'", STR(err->name));
+        return d;
+    case LOWER_ERROR_NAME_SHADOWS:
+        d.message = string_format("name '%S' shadows an existing binding", STR(err->name));
+        return d;
+    case LOWER_ERROR_TYPE_CYCLE: {
+        struct String_Buffer buf = string_buffer_new(512);
+        string_buffer_appendf(&buf, "type cycle detected\n");
+        lower_format_cycle_chain(&buf, unit, source, &err->cycle_chain);
+        d.message = string_buffer_to_string(&buf);
+        d.hint = string_from_static_c_string(
+            "break the cycle with a reference (use `&T` instead of `T`)");
+        return d;
+    }
+    case LOWER_ERROR_ASSIGNMENT_AS_EXPRESSION:
+        d.message = string_from_static_c_string("assignment cannot appear as an expression");
+        return d;
+    case LOWER_ERROR_RANGE_OUTSIDE_LOOP:
+        d.message =
+            string_from_static_c_string("range expressions are only valid in loop initializers");
+        return d;
+    case LOWER_ERROR_CONSTRUCT_SUBJECT_NOT_NAME:
+        d.message = string_from_static_c_string("construction subject must be a type name");
+        return d;
+    case LOWER_ERROR_TYPE_EXPRESSION_IN_BODY:
+        d.message =
+            string_from_static_c_string("type expression not allowed inside a function body");
+        return d;
+    case LOWER_ERROR_UNSUPPORTED_TOP_LEVEL:
+        d.message = string_from_static_c_string("unsupported top-level statement");
+        return d;
+    case LOWER_ERROR_UNKNOWN_LOOP_STYLE:
+        d.message = string_from_static_c_string("unknown loop style");
+        return d;
+    case LOWER_ERROR_UNKNOWN_COMPOUND_ASSIGN:
+        d.message = string_from_static_c_string("unknown compound assignment operator");
+        return d;
+    case LOWER_ERROR_UNIMPLEMENTED:
+        d.message = string_format("unimplemented: %S", STR(err->detail));
+        return d;
+    default:
+        d.message = string_from_static_c_string("unknown lower error");
+        return d;
+    }
 }
 
 bool
@@ -419,11 +483,13 @@ lower_intern_type_ref(struct Lower_Context* ctx, struct Tree_Type* tree_type)
         if (lower_type_lookup_by_name(ctx->unit, current->value.name.name, &id)) {
             ref.type_id = id;
         } else {
-            lower_emit_error(
-                ctx->unit, current->span,
-                string_concatenate(
-                    ARG_ASCII, "undefined type '", ARG_STRING, current->value.name.name, ARG_ASCII,
-                    "'", ARG_END));
+            lower_push_error(
+                ctx->unit,
+                (struct Lower_Error){
+                    .kind = LOWER_ERROR_UNDEFINED_TYPE,
+                    .span = current->span,
+                    .name = current->value.name.name,
+                });
         }
         return ref;
     }
@@ -438,10 +504,22 @@ lower_intern_type_ref(struct Lower_Context* ctx, struct Tree_Type* tree_type)
         return ref;
     }
     case TREE_TYPE_MAP:
-        lower_emit_error_c(ctx->unit, current->span, "unimplemented: map types");
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_UNIMPLEMENTED,
+                .span = current->span,
+                .detail = string_from_static_c_string("map types"),
+            });
         return ref;
     default:
-        lower_emit_error_c(ctx->unit, current->span, "unimplemented: this type form");
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_UNIMPLEMENTED,
+                .span = current->span,
+                .detail = string_from_static_c_string("this type form"),
+            });
         return ref;
     }
 }
@@ -494,10 +572,10 @@ lower_register_function_shell(struct Unit* unit, struct String name, struct Span
 {
     Function_Id existing;
     if (lower_function_lookup_by_name(unit, name, &existing)) {
-        lower_emit_error(
-            unit, span,
-            string_concatenate(
-                ARG_ASCII, "duplicate function '", ARG_STRING, name, ARG_ASCII, "'", ARG_END));
+        lower_push_error(
+            unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_DUPLICATE_FUNCTION, .span = span, .name = name });
         return false;
     }
 
@@ -517,10 +595,9 @@ lower_register_type_shell(struct Unit* unit, struct String name, struct Span spa
 {
     Type_Id existing;
     if (lower_type_lookup_by_name(unit, name, &existing)) {
-        lower_emit_error(
-            unit, span,
-            string_concatenate(
-                ARG_ASCII, "duplicate type '", ARG_STRING, name, ARG_ASCII, "'", ARG_END));
+        lower_push_error(
+            unit,
+            (struct Lower_Error){ .kind = LOWER_ERROR_DUPLICATE_TYPE, .span = span, .name = name });
         return false;
     }
 
@@ -570,11 +647,13 @@ lower_fill_type_body(struct Lower_Context* ctx, struct Type* type, struct Tree_T
             // aliases always need their target's full definition.
             array_push(&type->depends_on, &target_id);
         } else {
-            lower_emit_error(
-                ctx->unit, tree_type->span,
-                string_concatenate(
-                    ARG_ASCII, "undefined type '", ARG_STRING, tree_type->value.name.name,
-                    ARG_ASCII, "'", ARG_END));
+            lower_push_error(
+                ctx->unit,
+                (struct Lower_Error){
+                    .kind = LOWER_ERROR_UNDEFINED_TYPE,
+                    .span = tree_type->span,
+                    .name = tree_type->value.name.name,
+                });
         }
         break;
     }
@@ -628,7 +707,13 @@ lower_fill_type_body(struct Lower_Context* ctx, struct Type* type, struct Tree_T
         break;
     }
     case TREE_TYPE_CLASS:
-        lower_emit_error_c(ctx->unit, tree_type->span, "unimplemented: class types");
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_UNIMPLEMENTED,
+                .span = tree_type->span,
+                .detail = string_from_static_c_string("class types"),
+            });
         break;
     default: {
         // this is a type alias assigning a name to a structural type.
@@ -698,11 +783,19 @@ lower_pass_1_fill_bodies(struct Lower_Context* ctx, struct Tree* tree)
         }
 
         if (stmt->kind == TREE_STATEMENT_PRAGMA) {
-            lower_emit_error_c(ctx->unit, stmt->span, "unimplemented: top-level pragmas");
+            lower_push_error(
+                ctx->unit,
+                (struct Lower_Error){
+                    .kind = LOWER_ERROR_UNIMPLEMENTED,
+                    .span = stmt->span,
+                    .detail = string_from_static_c_string("top-level pragmas"),
+                });
             continue;
         }
 
-        lower_emit_error_c(ctx->unit, stmt->span, "unsupported top-level statement");
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){ .kind = LOWER_ERROR_UNSUPPORTED_TOP_LEVEL, .span = stmt->span });
     }
 }
 
@@ -778,12 +871,19 @@ lower_expression_binary_operation(struct Lower_Context* ctx, struct Tree_Express
     enum Binary_Operation op = tree_expr->value.binary_operator.operation;
     // TODO: maybe we want to support assignment expressions some day. today they aren't.
     if (op >= BINARY_ASSIGN) {
-        lower_emit_error_c(ctx->unit, tree_expr->span, "assignment cannot appear as an expression");
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_ASSIGNMENT_AS_EXPRESSION,
+                .span = tree_expr->span,
+            });
         return nil;
     }
     if (op == BINARY_RANGE) {
-        lower_emit_error_c(
-            ctx->unit, tree_expr->span, "range expressions are only valid in loop initializers");
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_RANGE_OUTSIDE_LOOP, .span = tree_expr->span });
         return nil;
     }
     return ir_make_binary(
@@ -800,7 +900,13 @@ lower_expression_call(struct Lower_Context* ctx, struct Tree_Expression* tree_ex
     // table to reorder, we only handle positional arguments today.
     FOR_EACH_ARRAY (struct String, name, &group->argument_names) {
         if (name->length > 0) {
-            lower_emit_error_c(ctx->unit, tree_expr->span, "unimplemented: named call arguments");
+            lower_push_error(
+                ctx->unit,
+                (struct Lower_Error){
+                    .kind = LOWER_ERROR_UNIMPLEMENTED,
+                    .span = tree_expr->span,
+                    .detail = string_from_static_c_string("named call arguments"),
+                });
             return nil;
         }
     }
@@ -819,16 +925,23 @@ lower_expression_construct(struct Lower_Context* ctx, struct Tree_Expression* tr
 {
     struct Tree_Expression* subject = tree_expr->value.construct.subject;
     if (!subject || subject->kind != TREE_EXPRESSION_NAME) {
-        lower_emit_error_c(ctx->unit, tree_expr->span, "construction subject must be a type name");
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_CONSTRUCT_SUBJECT_NOT_NAME,
+                .span = tree_expr->span,
+            });
         return nil;
     }
     Type_Id type_id;
     if (!lower_type_lookup_by_name(ctx->unit, subject->value.name.name, &type_id)) {
-        lower_emit_error(
-            ctx->unit, subject->span,
-            string_concatenate(
-                ARG_ASCII, "undefined type '", ARG_STRING, subject->value.name.name, ARG_ASCII, "'",
-                ARG_END));
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_UNDEFINED_TYPE,
+                .span = subject->span,
+                .name = subject->value.name.name,
+            });
         return nil;
     }
 
@@ -849,13 +962,15 @@ lower_expression_construct(struct Lower_Context* ctx, struct Tree_Expression* tr
     return ir_make_construct(type_id, fields, tree_expr->span);
 }
 
-// type-as-expression is only valid as the right-side of a top-level binding;
+// type-as-expression is only valid as the right-side of a top-level binding,
 // anywhere else it is an error.
 struct Expression*
 lower_expression_type(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
 {
-    lower_emit_error_c(
-        ctx->unit, tree_expr->span, "type expression not allowed inside a function body");
+    lower_push_error(
+        ctx->unit,
+        (struct Lower_Error){
+            .kind = LOWER_ERROR_TYPE_EXPRESSION_IN_BODY, .span = tree_expr->span });
     return nil;
 }
 
@@ -918,7 +1033,13 @@ lower_expression(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
     case TREE_EXPRESSION_MUST:
         // TODO: implement these
     default:
-        lower_emit_error_c(ctx->unit, tree_expr->span, "unimplemented: this expression kind");
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_UNIMPLEMENTED,
+                .span = tree_expr->span,
+                .detail = string_from_static_c_string("this expression kind"),
+            });
         return nil;
     }
 }
@@ -997,17 +1118,25 @@ lower_statement_declaration(struct Lower_Context* ctx, struct Tree_Statement* tr
     // TODO: multi-name decls like `var a, b int = ...` need to split into
     // N sibling declarations or introduce a temporary for the initializer.
     if (array_length(&decl->names) != 1) {
-        lower_emit_error_c(ctx->unit, tree_stmt->span, "unimplemented: multi-name declarations");
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_UNIMPLEMENTED,
+                .span = tree_stmt->span,
+                .detail = string_from_static_c_string("multi-name declarations"),
+            });
         return nil;
     }
 
     struct String name = *array_at(struct String, &decl->names, 0);
     if (lower_is_shadowing(ctx, name)) {
-        lower_emit_error(
-            ctx->unit, tree_stmt->span,
-            string_concatenate(
-                ARG_ASCII, "name '", ARG_STRING, name, ARG_ASCII, "' shadows an existing binding",
-                ARG_END));
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_NAME_SHADOWS,
+                .span = tree_stmt->span,
+                .name = name,
+            });
         return nil;
     }
     struct Type_Ref type = lower_intern_type_ref(ctx, decl->type);
@@ -1039,16 +1168,26 @@ lower_statement_expression(struct Lower_Context* ctx, struct Tree_Statement* tre
             if (!lhs_tree || lhs_tree->kind != TREE_EXPRESSION_NAME) {
                 // TODO: implement non-trivial lhs assignments like function
                 // calls for example. not too common but sometimes necessary.
-                lower_emit_error_c(
-                    ctx->unit, tree_stmt->span,
-                    "unimplemented: compound assignment with non-trivial lvalue");
+                lower_push_error(
+                    ctx->unit,
+                    (struct Lower_Error){
+                        .kind = LOWER_ERROR_UNIMPLEMENTED,
+                        .span = tree_stmt->span,
+                        .detail = string_from_static_c_string(
+                            "compound assignment with "
+                            "non-trivial lvalue"),
+                    });
                 return nil;
             }
 
             enum Binary_Operation simple_op = binary_operation_strip_assign(op);
             if (simple_op == BINARY_NONE) {
-                lower_emit_error_c(
-                    ctx->unit, tree_stmt->span, "unknown compound assignment operator");
+                lower_push_error(
+                    ctx->unit,
+                    (struct Lower_Error){
+                        .kind = LOWER_ERROR_UNKNOWN_COMPOUND_ASSIGN,
+                        .span = tree_stmt->span,
+                    });
                 return nil;
             }
 
@@ -1064,9 +1203,15 @@ lower_statement_expression(struct Lower_Context* ctx, struct Tree_Statement* tre
         struct Tree_Expression* subject = incdec->subject;
         if (!subject || subject->kind != TREE_EXPRESSION_NAME) {
             // TODO: handle this too, like compound assignments
-            lower_emit_error_c(
-                ctx->unit, tree_stmt->span,
-                "unimplemented: increment/decrement on non-trivial lvalue");
+            lower_push_error(
+                ctx->unit,
+                (struct Lower_Error){
+                    .kind = LOWER_ERROR_UNIMPLEMENTED,
+                    .span = tree_stmt->span,
+                    .detail = string_from_static_c_string(
+                        "increment/decrement on non-trivial "
+                        "lvalue"),
+                });
             return nil;
         }
 
@@ -1140,9 +1285,15 @@ lower_statement_loop(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt
         if (loop->style == TREE_STATEMENT_LOOP_STYLE_FOR_EACH) {
             if (!decl->initializer || decl->initializer->kind != TREE_EXPRESSION_BINARY_OPERATION
                 || decl->initializer->value.binary_operator.operation != BINARY_RANGE) {
-                lower_emit_error_c(
-                    ctx->unit, tree_stmt->span,
-                    "unimplemented: for-each over non-range collections");
+                lower_push_error(
+                    ctx->unit,
+                    (struct Lower_Error){
+                        .kind = LOWER_ERROR_UNIMPLEMENTED,
+                        .span = tree_stmt->span,
+                        .detail = string_from_static_c_string(
+                            "for-each over non-range "
+                            "collections"),
+                    });
                 return nil;
             }
             init = decl->initializer->value.binary_operator.left_operand;
@@ -1152,8 +1303,13 @@ lower_statement_loop(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt
         }
 
         if (array_length(&decl->names) != 1) {
-            lower_emit_error_c(
-                ctx->unit, tree_stmt->span, "unimplemented: for-loop with multi-name declaration");
+            lower_push_error(
+                ctx->unit,
+                (struct Lower_Error){
+                    .kind = LOWER_ERROR_UNIMPLEMENTED,
+                    .span = tree_stmt->span,
+                    .detail = string_from_static_c_string("for-loop with multi-name declaration"),
+                });
             return nil;
         }
         struct String name = *array_at(struct String, &decl->names, 0);
@@ -1163,11 +1319,13 @@ lower_statement_loop(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt
         lower_push_scope(ctx, SCOPE_TYPE_BLOCK);
 
         if (lower_is_shadowing(ctx, name)) {
-            lower_emit_error(
-                ctx->unit, tree_stmt->span,
-                string_concatenate(
-                    ARG_ASCII, "name '", ARG_STRING, name, ARG_ASCII,
-                    "' shadows an existing binding", ARG_END));
+            lower_push_error(
+                ctx->unit,
+                (struct Lower_Error){
+                    .kind = LOWER_ERROR_NAME_SHADOWS,
+                    .span = tree_stmt->span,
+                    .name = name,
+                });
             lower_pop_scope(ctx, SCOPE_TYPE_BLOCK);
             return nil;
         }
@@ -1226,7 +1384,10 @@ lower_statement_loop(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt
         return ir_make_block_statement(outer, tree_stmt->span);
     }
     default:
-        lower_emit_error_c(ctx->unit, tree_stmt->span, "unknown loop style");
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_UNKNOWN_LOOP_STYLE, .span = tree_stmt->span });
         return nil;
     }
 }
@@ -1267,7 +1428,13 @@ lower_statement(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
     case TREE_STATEMENT_CONTINUE:
         return lower_statement_continue(ctx, tree_stmt);
     default:
-        lower_emit_error_c(ctx->unit, tree_stmt->span, "unimplemented: this statement kind");
+        lower_push_error(
+            ctx->unit,
+            (struct Lower_Error){
+                .kind = LOWER_ERROR_UNIMPLEMENTED,
+                .span = tree_stmt->span,
+                .detail = string_from_static_c_string("this statement kind"),
+            });
         return nil;
     }
 }
@@ -1317,50 +1484,32 @@ lower_pass_2(struct Lower_Context* ctx)
 uint
 lower_span_to_line(struct String source, struct Span span)
 {
-    uint line = 1;
-    uint upto = span.start;
-    if (upto > string_length(source)) upto = string_length(source);
-    for (uint i = 0; i < upto; ++i) {
-        if (string_at(source, i) == '\n') line++;
-    }
+    uint line, column;
+    source_position_from_span(source, span, &line, &column);
     return line;
 }
 
-// builds a nice & friendly cycle detection message and emits it as a diagnostic.
-// `chain` is the path we walked while looking for the cycle, and
-// `cycle_start` is the chain index at which the cycle closes.
+// `chain` is the walk we took while looking for the cycle, `cycle_start` is
+// the index in it at which the cycle closes back on itself.
 void
-lower_report_type_cycle(
-    struct Unit* unit, struct Source_File source, struct _Array* chain, uint cycle_start)
+lower_report_type_cycle(struct Unit* unit, struct _Array* chain, uint cycle_start)
 {
-    struct String_Buffer buf = string_buffer_new(512);
-    string_buffer_append_c_str(&buf, "type cycle detected:\n");
-
     uint chain_len = array_length(chain);
+    Array(Type_Id) cycle_copy = array_new(Type_Id, chain_len - cycle_start);
     for (uint k = cycle_start; k < chain_len; ++k) {
-        Type_Id current_id = *array_at(Type_Id, chain, k);
-        Type_Id next_id;
-        if (k + 1 < chain_len)
-            next_id = *array_at(Type_Id, chain, k + 1);
-        else
-            next_id = *array_at(Type_Id, chain, cycle_start);
-
-        struct Type* current = *array_at(struct Type*, &unit->types.entries, current_id);
-        struct Type* next = *array_at(struct Type*, &unit->types.entries, next_id);
-        uint line = lower_span_to_line(source.source, current->span);
-
-        ascii line_buf[256];
-        snprintf(
-            line_buf, sizeof line_buf, "  %s (line %lu) depends on %s\n",
-            string_c_str(current->name), line, string_c_str(next->name));
-        string_buffer_append_c_str(&buf, line_buf);
+        Type_Id id = *array_at(Type_Id, chain, k);
+        array_push(&cycle_copy, &id);
     }
-    string_buffer_append_c_str(
-        &buf, "hint: break the cycle with a reference (use `&T` instead of `T`). :)");
 
     Type_Id origin_id = *array_at(Type_Id, chain, cycle_start);
     struct Type* origin = *array_at(struct Type*, &unit->types.entries, origin_id);
-    lower_emit_error(unit, origin->span, string_buffer_to_string(&buf));
+    lower_push_error(
+        unit,
+        (struct Lower_Error){
+            .kind = LOWER_ERROR_TYPE_CYCLE,
+            .span = origin->span,
+            .cycle_chain = cycle_copy,
+        });
 }
 
 // topologically sorting the unit's type dependency graph.
@@ -1370,7 +1519,7 @@ lower_report_type_cycle(
 // are always first to emit when their vertex in-degree is 0.
 // cycles reported as diagnostic, partial emission order will still be completed.
 void
-lower_topological_sort_dependency_graph(struct Unit* unit, struct Source_File source)
+lower_topological_sort_dependency_graph(struct Unit* unit)
 {
     const uint done_sentinel = (uint)-1; // marks vertecies that have been processed
 
@@ -1461,7 +1610,7 @@ lower_topological_sort_dependency_graph(struct Unit* unit, struct Source_File so
             }
         }
         if (found_at < chain_len) {
-            lower_report_type_cycle(unit, source, &chain, found_at);
+            lower_report_type_cycle(unit, &chain, found_at);
             return;
         }
 
@@ -1471,7 +1620,7 @@ lower_topological_sort_dependency_graph(struct Unit* unit, struct Source_File so
 }
 
 void
-lower_tree(struct Tree* tree, struct Source_File source, struct Unit* unit)
+lower_tree(struct Tree* tree, struct Unit* unit)
 {
     unit->types.entries = array_new(struct Type*, 256);
     unit->types.by_hash = array_new(struct Type_Hash_To_Id, 256);
@@ -1483,7 +1632,7 @@ lower_tree(struct Tree* tree, struct Source_File source, struct Unit* unit)
     unit->imports = array_new(struct Import, 32);
     unit->type_emission_order = array_new(Type_Id, 256);
     unit->had_error = false;
-    unit->diagnostics = array_new(struct Diagnostic, 64);
+    unit->lower_errors = array_new(struct Lower_Error, 64);
 
     unit->types.primitive_int_id = lower_seed_primitive(unit, "int");
     unit->types.primitive_uint_id = lower_seed_primitive(unit, "uint");
@@ -1496,7 +1645,6 @@ lower_tree(struct Tree* tree, struct Source_File source, struct Unit* unit)
 
     struct Lower_Context ctx = {
         .unit = unit,
-        .source = source,
         .synthetic_type_counter = 0,
         .synthetic_counter = 0,
         .scope_stack = array_new(struct Scope, 32),
@@ -1510,5 +1658,5 @@ lower_tree(struct Tree* tree, struct Source_File source, struct Unit* unit)
         lower_pass_1(&ctx, tree);
         lower_pass_2(&ctx);
     }
-    lower_topological_sort_dependency_graph(unit, source);
+    lower_topological_sort_dependency_graph(unit);
 }