diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-07-15 17:23:42 +0200 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-07-15 17:23:42 +0200 |
| commit | e7265af15f77abf60a899b3d53a59652b5ca71ea (patch) | |
| tree | 50b5594c4517da87b185268e709194e964c236af /boot/semantic.cc | |
| parent | 308a2addf655fe45f2098f8542f0ff1290612cd9 (diff) | |
| download | elna-e7265af15f77abf60a899b3d53a59652b5ca71ea.tar.gz | |
Track identifier positions for the diagnostics
Diffstat (limited to 'boot/semantic.cc')
| -rw-r--r-- | boot/semantic.cc | 331 |
1 files changed, 242 insertions, 89 deletions
diff --git a/boot/semantic.cc b/boot/semantic.cc index dd9022b..031adfc 100644 --- a/boot/semantic.cc +++ b/boot/semantic.cc @@ -22,8 +22,8 @@ along with GCC; see the file COPYING3. If not see namespace elna::boot { declaration_error::declaration_error(const kind error_kind, - const std::string& identifier, const struct position position) - : error(position), identifier(identifier), error_kind(error_kind) + const boot::identifier& identifier) + : error(identifier.position()), identifier(identifier.name()), error_kind(error_kind) { } @@ -33,8 +33,6 @@ namespace elna::boot { case kind::undeclared: return "Type '" + identifier + "' not declared"; - case kind::already: - return "Symbol '" + identifier + "' has been already declared"; case kind::local_export: return "Local symbol '" + this->identifier + "' cannot be exported"; default: @@ -42,38 +40,107 @@ namespace elna::boot } } - type_expectation_error::type_expectation_error(const kind error_kind, const struct position position) - : error(position), error_kind(error_kind) + redefinition_error::redefinition_error(const boot::identifier& identifier, + std::optional<source_position> original) + : error(identifier.position()), identifier(identifier.name()), original(original) { } - std::string type_expectation_error::what() const + std::string redefinition_error::what() const { - switch (this->error_kind) + return "Symbol '" + identifier + "' has been already defined"; + } + + std::optional<std::pair<std::string, source_position>> + redefinition_error::note() const + { + if (original.has_value() && original->start().available()) { - case kind::assignment: - return "Assinging an incompatible type"; - case kind::result: - return "Procedure return type and type of the return expression are not compatible"; - case kind::argument: - return "Argument type does not match parameter"; - default: - __builtin_unreachable(); + return std::make_pair("previously declared here", *original); + } + return std::nullopt; + } + + type_mismatch_error::type_mismatch_error(const source_position position, + type expected, type actual) + : error(position), expected(expected), actual(actual) + { + } + + std::string type_mismatch_error::what() const + { + return "Expected type '" + expected.to_string() + + "', but got '" + actual.to_string() + "'"; + } + + field_not_found_error::field_not_found_error(const identifier& field_name, + type composite_type) + : error(field_name.position()), field_name(field_name.name()), composite_type(composite_type) + { + } + + std::string field_not_found_error::what() const + { + type resolved = inner_aliased_type(composite_type); + bool is_enum = resolved.get<enumeration_type>() != nullptr; + bool is_record = resolved.get<record_type>() != nullptr; + + if (is_enum || is_record) + { + std::string message = is_enum ? "Enumeration" : "Record"; + + if (auto alias = composite_type.get<alias_type>()) + { + message += " '" + alias->name + "'"; + } + message += " does not have a "; + message += is_enum ? "member" : "field"; + message += " named '" + field_name + "'"; + return message; } + return "Type '" + composite_type.to_string() + + "' does not have a field named '" + field_name + "'"; } - field_duplication_error::field_duplication_error(const std::string& field_name, const struct position position) - : error(position), field_name(field_name) + duplicate_member_error::duplicate_member_error(const boot::identifier& member_name, + type aggregate, std::optional<source_position> original, + std::optional<std::string> base_name) + : error(member_name.position()), member_name(member_name.name()), aggregate(aggregate), + original(original), base_name(base_name) { } - std::string field_duplication_error::what() const + std::string duplicate_member_error::what() const { - return "Repeated field name '" + field_name + "'"; + type resolved = inner_aliased_type(aggregate); + bool is_enum = resolved.get<enumeration_type>() != nullptr; + std::string kind = is_enum ? "member" : "field"; + std::string message = is_enum ? "Enumeration" : "Record"; + + if (auto alias = aggregate.get<alias_type>()) + { + message += " '" + alias->name + "'"; + } + message += " already has a " + kind + " named '" + member_name + "'"; + + if (base_name.has_value()) + { + message += " (defined in base type '" + *base_name + "')"; + } + return message; + } + + std::optional<std::pair<std::string, source_position>> duplicate_member_error::note() const + { + if (original.has_value() && original->start().available()) + { + return std::make_pair("previously declared here", *original); + } + return std::nullopt; } cyclic_declaration_error::cyclic_declaration_error(const std::vector<std::string>& cycle, - const struct position position) + const source_position position) : error(position), cycle(cycle) { } @@ -91,28 +158,36 @@ namespace elna::boot return message; } - return_error::return_error(const std::string& identifier, const struct position position) - : error(position), identifier(identifier) + return_error::return_error(const std::string& identifier, const source_position position, + type return_type) + : error(position), identifier(identifier), return_type(return_type) { } std::string return_error::what() const { - return "Procedure '" + this->identifier + "' is expected to return, but does not have a return statement"; + if (!return_type.empty()) + { + return "Procedure '" + this->identifier + + "' does not return a value, but return expression has type '" + + return_type.to_string() + "'"; + } + return "Procedure '" + this->identifier + + "' is expected to return, but does not have a return statement"; } - base_type_error::base_type_error(const std::string& base, const struct position position) - : error(position), base(base) + base_type_error::base_type_error(type actual, const source_position position) + : error(position), actual(actual) { } std::string base_type_error::what() const { - return "Record base type '" + this->base + "' is not a record."; + return "'" + actual.to_string() + "' is not a record type"; } argument_count_error::argument_count_error(std::size_t expected, std::size_t actual, - const struct position position) + const source_position position) : error(position), expected(expected), actual(actual) { } @@ -138,7 +213,7 @@ namespace elna::boot void type_analysis_visitor::visit(procedure_declaration *declaration) { - this->current_procedure = this->bag.lookup(declaration->identifier.name)->is_procedure(); + this->current_procedure = this->bag.lookup(declaration->identifier.name())->is_procedure(); if (declaration->body.has_value()) { @@ -157,17 +232,19 @@ namespace elna::boot { if (!is_assignable_from(return_type, return_expr->type_decoration)) { - add_error<type_expectation_error>(type_expectation_error::kind::result, return_expr->position()); + add_error<type_mismatch_error>( + return_expr->position(), return_type, return_expr->type_decoration); } } else { - add_error<type_expectation_error>(type_expectation_error::kind::result, return_expr->position()); + add_error<return_error>(declaration->identifier.name(), + return_expr->position(), return_expr->type_decoration); } } else if (declaration->heading().return_type.proper_type != nullptr) { - add_error<return_error>(declaration->identifier.name, declaration->position()); + add_error<return_error>(declaration->identifier.name(), declaration->position()); } this->bag.leave(); } @@ -185,7 +262,8 @@ namespace elna::boot if (!is_assignable_from(statement->lvalue().type_decoration, statement->rvalue().type_decoration)) { - add_error<type_expectation_error>(type_expectation_error::kind::assignment, statement->position()); + add_error<type_mismatch_error>(statement->position(), + statement->lvalue().type_decoration, statement->rvalue().type_decoration); } } @@ -199,11 +277,12 @@ namespace elna::boot } for (const identifier_definition& variable_identifier : declaration->identifiers) { - auto variable_symbol = this->bag.lookup(variable_identifier.name)->is_variable(); + auto variable_symbol = this->bag.lookup(variable_identifier.name())->is_variable(); if (!is_assignable_from(variable_symbol->symbol, declaration->initializer->type_decoration)) { - add_error<type_expectation_error>(type_expectation_error::kind::assignment, - declaration->initializer->position()); + add_error<type_mismatch_error>( + declaration->initializer->position(), + variable_symbol->symbol, declaration->initializer->type_decoration); } } } @@ -219,8 +298,8 @@ namespace elna::boot { if (!is_assignable_from(condition_type, case_label->type_decoration)) { - add_error<type_expectation_error>(type_expectation_error::kind::assignment, - case_label->position()); + add_error<type_mismatch_error>( + case_label->position(), condition_type, case_label->type_decoration); } } } @@ -245,7 +324,7 @@ namespace elna::boot void type_analysis_visitor::visit(type_declaration *declaration) { std::vector<std::string> alias_path; - auto unresolved_type = this->bag.lookup(declaration->identifier.name)->is_type()->symbol.get<alias_type>(); + auto unresolved_type = this->bag.lookup(declaration->identifier.name())->is_type()->symbol.get<alias_type>(); if (!check_unresolved_symbol(unresolved_type, alias_path)) { @@ -261,10 +340,10 @@ namespace elna::boot { if (expression->base.has_value()) { - type base_type = inner_aliased_type(this->bag.lookup(expression->base.value())->is_type()->symbol); + type base_type = inner_aliased_type(this->bag.lookup(expression->base.value().name())->is_type()->symbol); if (base_type.get<record_type>() == nullptr) { - add_error<base_type_error>(expression->base.value(), expression->position()); + add_error<base_type_error>(base_type, expression->position()); } } walking_visitor::visit(expression); @@ -285,8 +364,9 @@ namespace elna::boot (*argument_iterator)->accept(this); if (!is_assignable_from(*type_iterator, (*argument_iterator)->type_decoration)) { - add_error<type_expectation_error>(type_expectation_error::kind::argument, - (*argument_iterator)->position()); + add_error<type_mismatch_error>( + (*argument_iterator)->position(), *type_iterator, + (*argument_iterator)->type_decoration); } ++argument_iterator; ++type_iterator; @@ -305,19 +385,22 @@ namespace elna::boot if (record == nullptr) { - add_error<type_expectation_error>(type_expectation_error::kind::result, expression->position()); + add_error<type_mismatch_error>( + expression->position(), type(std::make_shared<record_type>()), + expression->type_decoration); return; } for (const field_initializer& initializer : expression->field_initializers) { for (const type_field& field : record->fields) { - if (field.first == initializer.name) + if (field.first == initializer.name()) { - if (!is_assignable_from(field.second, initializer.value->type_decoration)) + if (!is_assignable_from(field.second, initializer.value().type_decoration)) { - add_error<type_expectation_error>(type_expectation_error::kind::argument, - initializer.value->position()); + add_error<type_mismatch_error>( + initializer.value().position(), field.second, + initializer.value().type_decoration); } break; } @@ -331,7 +414,9 @@ namespace elna::boot if (array == nullptr) { - add_error<type_expectation_error>(type_expectation_error::kind::result, expression->position()); + add_error<type_mismatch_error>( + expression->position(), type(std::make_shared<array_type>(type(), 0)), + expression->type_decoration); return; } if (expression->elements.size() > array->size) @@ -344,8 +429,8 @@ namespace elna::boot { if (!is_assignable_from(array->base, element->type_decoration)) { - add_error<type_expectation_error>(type_expectation_error::kind::argument, - element->position()); + add_error<type_mismatch_error>( + element->position(), array->base, element->type_decoration); } } } @@ -382,7 +467,7 @@ namespace elna::boot for (auto& parameter_name : parameter_names) { result_type.first.parameters.push_back(this->current_type); - result_type.second.push_back(parameter_name); + result_type.second.push_back(parameter_name.name()); } } return result_type; @@ -468,11 +553,12 @@ namespace elna::boot void name_analysis_visitor::visit(type_declaration *declaration) { walking_visitor::visit(declaration); - auto resolved = this->bag.resolve(declaration->identifier.name, this->current_type); + auto resolved = this->bag.resolve(declaration->identifier.name(), this->current_type); auto info = std::make_shared<type_info>(type(resolved)); - info->exported = declaration->identifier.exported; - this->bag.enter(declaration->identifier.name, info); + info->exported = declaration->identifier.exported(); + info->position.emplace(declaration->position()); + this->bag.enter(declaration->identifier.name(), info); } void name_analysis_visitor::visit(pointer_type_expression *expression) @@ -492,7 +578,8 @@ namespace elna::boot /** * Collects field names from a record type recursively, base first. */ - static void collect_field_names(const type& composite_type, std::set<std::string>& names) + static void collect_field_names(const type& composite_type, + std::map<std::string, field_origin>& names) { auto record = inner_aliased_type(composite_type).get<record_type>(); if (record == nullptr) @@ -505,12 +592,14 @@ namespace elna::boot } for (auto& field : record->fields) { - names.insert(field.first); + names.insert({ field.first, field_origin{ std::nullopt, composite_type } }); } } std::vector<type_field> name_analysis_visitor::build_composite_type( - const std::vector<field_declaration>& fields, std::set<std::string>& field_names) + const std::vector<field_declaration>& fields, + std::map<std::string, field_origin>& field_names, + type aggregate) { std::vector<type_field> result; @@ -519,13 +608,27 @@ namespace elna::boot field.second->accept(this); for (auto& field_name : field.first) { - if (!field_names.insert(field_name).second) + auto existing = field_names.find(field_name.name()); + if (existing != field_names.end()) { - add_error<field_duplication_error>(field_name, field.second->position()); + std::optional<std::string> base_name; + + if (!existing->second.declaration.has_value() + && !existing->second.base_type.empty()) + { + if (auto alias = existing->second.base_type.get<alias_type>()) + { + base_name = alias->name; + } + } + add_error<duplicate_member_error>(field_name, aggregate, + existing->second.declaration, base_name); } else { - result.push_back(std::make_pair(field_name, this->current_type)); + field_names.insert({ field_name.name(), + field_origin{ field.second->position(), type() } }); + result.push_back(std::make_pair(field_name.name(), this->current_type)); } } } @@ -537,11 +640,11 @@ namespace elna::boot std::shared_ptr<record_type> result_type; if (expression->base.has_value()) { - if (auto unresolved_alias = this->bag.declared(expression->base.value())) + if (auto unresolved_alias = this->bag.declared(expression->base.value().name())) { result_type = std::make_shared<record_type>(type(unresolved_alias)); } - else if (auto base_symbol = this->bag.lookup(expression->base.value())) + else if (auto base_symbol = this->bag.lookup(expression->base.value().name())) { if (auto base_type_info = base_symbol->is_type()) { @@ -549,7 +652,17 @@ namespace elna::boot } else { - add_error<base_type_error>(expression->base.value(), expression->position()); + type actual; + + if (auto var = base_symbol->is_variable()) + { + actual = var->symbol; + } + else if (auto proc = base_symbol->is_procedure()) + { + actual = type(std::make_shared<procedure_type>(proc->symbol)); + } + add_error<base_type_error>(actual, expression->position()); this->current_type = type(); return; } @@ -557,7 +670,7 @@ namespace elna::boot else { add_error<declaration_error>(declaration_error::kind::undeclared, - expression->base.value(), expression->position()); + expression->base.value()); this->current_type = type(); return; } @@ -567,30 +680,35 @@ namespace elna::boot result_type = std::make_shared<record_type>(); } - std::set<std::string> field_names; + std::map<std::string, field_origin> field_names; collect_field_names(result_type->base, field_names); - result_type->fields = build_composite_type(expression->fields, field_names); + result_type->fields = build_composite_type(expression->fields, field_names, type(result_type)); this->current_type = type(result_type); } void name_analysis_visitor::visit(record_constructor_expression *expression) { - if (auto type_symbol = this->bag.lookup(expression->type_name)) + if (auto type_symbol = this->bag.lookup(expression->type_name.name())) { if (auto type_info = type_symbol->is_type()) { expression->type_decoration = type_info->symbol; } } + else + { + add_error<declaration_error>(declaration_error::kind::undeclared, + expression->type_name); + } for (const field_initializer& initializer : expression->field_initializers) { - initializer.value->accept(this); + initializer.value().accept(this); if (!expression->type_decoration.empty() - && lookup_field(expression->type_decoration, initializer.name).empty()) + && lookup_field(expression->type_decoration, initializer.name()).empty()) { add_error<declaration_error>(declaration_error::kind::undeclared, - initializer.name, expression->position()); + initializer.id()); } } } @@ -616,19 +734,42 @@ namespace elna::boot void name_analysis_visitor::visit(enumeration_type_expression *expression) { - std::shared_ptr<enumeration_type> result_type = std::make_shared<enumeration_type>(expression->members); + std::vector<std::string> member_names; + for (auto& member : expression->members) + { + member_names.emplace_back(member.name()); + } + std::shared_ptr<enumeration_type> result_type = std::make_shared<enumeration_type>( + member_names); + std::map<std::string, source_position> seen; + type aggregate(result_type); + for (auto& member : expression->members) + { + auto existing = seen.find(member.name()); + if (existing != seen.end()) + { + add_error<duplicate_member_error>(member, aggregate, existing->second); + } + else + { + seen.insert({ member.name(), member.position() }); + } + } this->current_type = type(result_type); } std::shared_ptr<variable_info> name_analysis_visitor::register_variable(const std::string& name, - const bool is_extern, const struct position position) + const bool is_extern, const source_position position) { auto variable_symbol = std::make_shared<variable_info>(this->current_type, is_extern); + variable_symbol->position.emplace(position); if (!this->bag.enter(name, variable_symbol)) { - add_error<declaration_error>(declaration_error::kind::already, name, position); + auto original = this->bag.lookup(name); + add_error<redefinition_error>(boot::identifier(name, position), + original->position); } return variable_symbol; } @@ -644,9 +785,9 @@ namespace elna::boot } for (const identifier_definition& variable_identifier : declaration->identifiers) { - auto variable_symbol = register_variable(variable_identifier.name, declaration->is_extern, + auto variable_symbol = register_variable(variable_identifier.name(), declaration->is_extern, declaration->position()); - variable_symbol->exported = variable_identifier.exported; + variable_symbol->exported = variable_identifier.exported(); } } @@ -655,8 +796,9 @@ namespace elna::boot walking_visitor::visit(declaration); auto constant_symbol = std::make_shared<constant_info>(this->current_literal); - constant_symbol->exported = declaration->identifier.exported; - this->bag.enter(declaration->identifier.name, constant_symbol); + constant_symbol->exported = declaration->identifier.exported(); + constant_symbol->position.emplace(declaration->position()); + this->bag.enter(declaration->identifier.name(), constant_symbol); } void name_analysis_visitor::visit(procedure_declaration *declaration) @@ -701,8 +843,9 @@ namespace elna::boot { info = std::make_shared<procedure_info>(heading, std::move(parameter_names)); } - info->exported = declaration->identifier.exported; - this->bag.enter(declaration->identifier.name, info); + info->exported = declaration->identifier.exported(); + info->position.emplace(declaration->position()); + this->bag.enter(declaration->identifier.name(), info); } void name_analysis_visitor::visit(procedure_call *call) @@ -775,6 +918,11 @@ namespace elna::boot { trait->type_decoration = trait->types.empty() ? type() : trait->types.front(); } + else + { + add_error<declaration_error>(declaration_error::kind::undeclared, + trait->name); + } } void name_analysis_visitor::visit(binary_expression *expression) @@ -841,13 +989,18 @@ namespace elna::boot void name_analysis_visitor::visit(field_access_expression *expression) { walking_visitor::visit(expression); - expression->type_decoration = lookup_field(expression->base().type_decoration, expression->field()); + expression->type_decoration = lookup_field(expression->base().type_decoration, expression->field().name()); auto is_designator = expression->base().is_designator(); if (expression->type_decoration.empty() && is_designator != nullptr && is_designator->is_named() != nullptr) { expression->type_decoration = this->current_type; } + if (expression->type_decoration.empty()) + { + add_error<field_not_found_error>(expression->field(), + expression->base().type_decoration); + } } void name_analysis_visitor::visit(dereference_expression *expression) @@ -896,7 +1049,7 @@ namespace elna::boot else { add_error<declaration_error>(declaration_error::kind::undeclared, - expression->name, expression->position()); + boot::identifier(expression->name, expression->position())); } } @@ -969,12 +1122,12 @@ namespace elna::boot void declaration_visitor::visit(type_declaration *declaration) { - const std::string& type_identifier = declaration->identifier.name; + const std::string& type_identifier = declaration->identifier.name(); if (!this->unresolved.insert({ type_identifier, std::make_shared<alias_type>(type_identifier) }).second) { - add_error<declaration_error>(declaration_error::kind::already, - declaration->identifier.name, declaration->position()); + add_error<redefinition_error>(declaration->identifier.id(), + declaration->position()); } } @@ -998,20 +1151,20 @@ namespace elna::boot { for (const identifier_definition& variable_identifier : declaration->identifiers) { - if (variable_identifier.exported) + if (variable_identifier.exported()) { add_error<declaration_error>(declaration_error::kind::local_export, - variable_identifier.name, declaration->position()); + variable_identifier.id()); } } } void declaration_visitor::visit(constant_declaration *declaration) { - if (declaration->identifier.exported) + if (declaration->identifier.exported()) { add_error<declaration_error>(declaration_error::kind::local_export, - declaration->identifier.name, declaration->position()); + declaration->identifier.id()); } } |
