diff options
| author | Mel <mel@rnrd.eu> | 2026-05-24 18:18:23 +0200 |
|---|---|---|
| committer | Mel <mel@rnrd.eu> | 2026-05-24 18:18:23 +0200 |
| commit | a76fd9b5af153773273121c7aec2a9cbeed1dfb7 (patch) | |
| tree | a6c619e44df4f38f60c457b9122e207b0a3c09f0 /boot/lower.c | |
| parent | 8c4c219c945dfb41415e04654f488f7880e58ca2 (diff) | |
| download | catskill-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.c | 378 |
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); } |
