diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-07-17 13:49:20 +0200 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-07-17 13:49:20 +0200 |
| commit | 44a0c221d51c9aa036b47b218a6caea4424f5996 (patch) | |
| tree | 0a83ceb4b938b893f393f647e355546d24b65dd4 /boot/type_check.cc | |
| parent | a0e1740227535adc3151c02eb102c4f96b4f9731 (diff) | |
| download | elna-44a0c221d51c9aa036b47b218a6caea4424f5996.tar.gz | |
Handle const Pointer as const pointer to const data
Diffstat (limited to 'boot/type_check.cc')
| -rw-r--r-- | boot/type_check.cc | 573 |
1 files changed, 573 insertions, 0 deletions
diff --git a/boot/type_check.cc b/boot/type_check.cc new file mode 100644 index 0000000..3667007 --- /dev/null +++ b/boot/type_check.cc @@ -0,0 +1,573 @@ +/* Type analysis. + Copyright (C) 2025 Free Software Foundation, Inc. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "elna/boot/type_check.h" + +#include <algorithm> + +namespace elna::boot +{ + 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() + "'"; + } + + constant_assignment_error::constant_assignment_error(const source_position position, + type assignee) + : error(position), assignee(assignee) + { + } + + std::string constant_assignment_error::what() const + { + return "Cannot assign to a value of type '" + assignee.to_string() + + "', because it is constant or contains constant members"; + } + + 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 = resolve_underlying_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 + "'"; + } + + 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 duplicate_member_error::what() const + { + type resolved = resolve_underlying_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 source_position position) + : error(position), cycle(cycle) + { + } + + std::string cyclic_declaration_error::what() const + { + auto segment = std::cbegin(this->cycle); + std::string message = "Type declaration forms a cycle: " + *segment; + + ++segment; + for (; segment != std::cend(this->cycle); ++segment) + { + message += " -> " + *segment; + } + return message; + } + + 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 + { + 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(type actual, const source_position position) + : error(position), actual(actual) + { + } + + std::string base_type_error::what() const + { + return "'" + actual.to_string() + "' is not a record type"; + } + + argument_count_error::argument_count_error(std::size_t expected, std::size_t actual, + const source_position position) + : error(position), expected(expected), actual(actual) + { + } + + std::string argument_count_error::what() const + { + if (actual > expected) + { + return "Too many arguments, expected " + std::to_string(expected) + + ", got " + std::to_string(actual); + } + else + { + return "Too few arguments, expected " + std::to_string(expected) + + ", got " + std::to_string(actual); + } + } + + unsupported_trait_type_error::unsupported_trait_type_error(const identifier& trait, + type actual) + : error(trait.position()), actual(actual), trait_name(trait.name()) + { + } + + std::string unsupported_trait_type_error::what() const + { + return "Type '" + actual.to_string() + + "' does not support trait '#" + trait_name + "'"; + } + + /* + * Whether the type itself is constant or has a constant member at any + * nesting level, so that values of this type cannot be reassigned as a + * whole. Pointers to constants do not make the type itself constant. + */ + static bool contains_constant_member(const type& checked) + { + auto referent = resolve_aliases(checked); + + if (referent.get<constant_type>() != nullptr) + { + return true; + } + else if (auto record = referent.get<record_type>()) + { + for (const type_field& field : record->fields) + { + if (contains_constant_member(field.second)) + { + return true; + } + } + return !record->base.empty() && contains_constant_member(record->base); + } + else if (auto array = referent.get<array_type>()) + { + return contains_constant_member(array->base); + } + return false; + } + + bool type_analysis_visitor::check_unresolved_symbol(std::shared_ptr<alias_type> alias, + std::vector<std::string>& alias_path) + { + if (std::find(std::cbegin(alias_path), std::cend(alias_path), alias->name) != std::cend(alias_path)) + { + return false; + } + alias_path.push_back(alias->name); + + if (auto another_alias = alias->reference.get<alias_type>()) + { + return check_unresolved_symbol(another_alias, alias_path); + } + return true; + } + + /* + * Checks whether derived has base in its record parent chain. + * + * Base record type must not be null. Derived record type may be null. + */ + static bool is_base_of(const std::shared_ptr<record_type>& base, + const std::shared_ptr<record_type>& derived) + { + if (derived != nullptr) + { + if (auto current_record = resolve_underlying_type(derived->base).get<record_type>()) + { + return current_record == base || is_base_of(base, current_record); + } + } + return false; + } + + assign_check::verdict assign_check::guard_const_laundering() + { + if (!is_primitive_type(ctx.aliased_assignee, "Pointer")) + { + return verdict::pass; + } + if (auto ptr = ctx.aliased_assignment.get<pointer_type>(); + ptr != nullptr && resolve_aliases(ptr->base).get<constant_type>() != nullptr) + { + return verdict::reject; + } + if (auto const_assign = ctx.aliased_assignment.get<constant_type>(); + const_assign != nullptr && is_primitive_type(const_assign->unqualified, "Pointer")) + { + return verdict::reject; + } + return verdict::pass; + } + + assign_check::verdict assign_check::check_exact_match() + { + return ctx.resolved_assignee == ctx.resolved_assignment ? verdict::accept : verdict::pass; + } + + assign_check::verdict assign_check::check_pointer_hatch() + { + if (is_primitive_type(ctx.resolved_assignee, "Pointer") + && is_any_pointer_type(ctx.resolved_assignment)) + { + return verdict::accept; + } + if (is_primitive_type(ctx.resolved_assignment, "Pointer") + && is_any_pointer_type(ctx.resolved_assignee)) + { + return verdict::accept; + } + return verdict::pass; + } + + assign_check::verdict assign_check::check_pointer_conversion() + { + auto assignee_ptr = ctx.resolved_assignee.get<pointer_type>(); + auto assignment_ptr = ctx.resolved_assignment.get<pointer_type>(); + + if (assignee_ptr == nullptr || assignment_ptr == nullptr) + { + return verdict::reject; + } + auto assignee_pointee_const = resolve_aliases(assignee_ptr->base).get<constant_type>(); + auto assignment_pointee_const = resolve_aliases(assignment_ptr->base).get<constant_type>(); + + // Constness can be added at the first indirection level, but not removed. + if (assignee_pointee_const != nullptr + && assignee_pointee_const->unqualified == assignment_ptr->base) + { + return verdict::accept; + } + if (assignment_pointee_const != nullptr && assignee_pointee_const == nullptr) + { + return verdict::reject; + } + // A pointer to a record can be assigned to a pointer to its base type. + if (auto assignee_record = resolve_underlying_type(assignee_ptr->base).get<record_type>()) + { + return is_base_of(assignee_record, + resolve_underlying_type(assignment_ptr->base).get<record_type>()) + ? verdict::accept : verdict::pass; + } + return verdict::pass; + } + + bool assign_check::run() + { + for (auto handler : {&assign_check::guard_const_laundering, + &assign_check::check_exact_match, + &assign_check::check_pointer_hatch, + &assign_check::check_pointer_conversion}) + { + switch ((this->*handler)()) + { + case verdict::accept: return true; + case verdict::reject: return false; + case verdict::pass:; + } + } + return false; + } + + bool type_analysis_visitor::is_assignable_from(const type& assignee, const type& assignment) + { + return assign_check{ + resolve_aliases(assignee), + resolve_aliases(assignment), + resolve_underlying_type(assignee), + resolve_underlying_type(assignment) + }.run(); + } + + type_analysis_visitor::type_analysis_visitor(symbol_bag bag) + : error_container(), bag(bag) + { + } + + void type_analysis_visitor::visit(procedure_declaration *declaration) + { + this->current_procedure = this->bag.lookup(declaration->identifier.name())->is_procedure(); + + if (declaration->body.has_value()) + { + this->bag.enter(this->current_procedure->scope); + } + walking_visitor::visit(declaration); + + if (declaration->body.has_value()) + { + if (declaration->body.value().return_expression != nullptr) + { + expression *return_expr = declaration->body.value().return_expression; + type return_type = this->current_procedure->symbol.return_type.proper_type; + + if (!return_type.empty()) + { + if (!is_assignable_from(return_type, return_expr->type_decoration)) + { + add_error<type_mismatch_error>( + return_expr->position(), return_type, return_expr->type_decoration); + } + } + else + { + 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()); + } + this->bag.leave(); + } + this->current_procedure.reset(); + } + + void type_analysis_visitor::visit(unit *unit) + { + walking_visitor::visit(unit); + } + + void type_analysis_visitor::visit(assign_statement *statement) + { + walking_visitor::visit(statement); + + if (contains_constant_member(statement->lvalue().type_decoration)) + { + add_error<constant_assignment_error>(statement->position(), + statement->lvalue().type_decoration); + } + else if (!is_assignable_from(statement->lvalue().type_decoration, statement->rvalue().type_decoration)) + { + add_error<type_mismatch_error>(statement->position(), + statement->lvalue().type_decoration, statement->rvalue().type_decoration); + } + } + + void type_analysis_visitor::visit(variable_declaration *declaration) + { + walking_visitor::visit(declaration); + + if (declaration->initializer == nullptr) + { + return; + } + for (const identifier_definition& variable_identifier : declaration->identifiers) + { + 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_mismatch_error>( + declaration->initializer->position(), + variable_symbol->symbol, declaration->initializer->type_decoration); + } + } + } + + void type_analysis_visitor::visit(case_statement *statement) + { + walking_visitor::visit(statement); + type condition_type = resolve_underlying_type(statement->condition().type_decoration); + + for (const switch_case& case_block : statement->cases) + { + for (expression *const case_label : case_block.labels) + { + if (!is_assignable_from(condition_type, case_label->type_decoration)) + { + add_error<type_mismatch_error>( + case_label->position(), condition_type, case_label->type_decoration); + } + } + } + } + + 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>(); + + if (!check_unresolved_symbol(unresolved_type, alias_path)) + { + add_error<cyclic_declaration_error>(alias_path, declaration->position()); + } + else + { + walking_visitor::visit(declaration); + } + } + + void type_analysis_visitor::visit(record_type_expression *expression) + { + if (expression->base.has_value()) + { + type base_type = resolve_underlying_type(this->bag.lookup(expression->base.value().name())->is_type()->symbol); + if (base_type.get<record_type>() == nullptr) + { + add_error<base_type_error>(base_type, expression->position()); + } + } + walking_visitor::visit(expression); + } + + void type_analysis_visitor::visit(procedure_call *call) + { + call->callable().accept(this); + + if (auto procedure = call->callable().type_decoration.get<procedure_type>()) + { + std::vector<expression *>::const_iterator argument_iterator = std::cbegin(call->arguments); + std::vector<type>::const_iterator type_iterator = std::cbegin(procedure->parameters); + + while (argument_iterator != std::cend(call->arguments) + && type_iterator != std::cend(procedure->parameters)) + { + (*argument_iterator)->accept(this); + if (!is_assignable_from(*type_iterator, (*argument_iterator)->type_decoration)) + { + add_error<type_mismatch_error>( + (*argument_iterator)->position(), *type_iterator, + (*argument_iterator)->type_decoration); + } + ++argument_iterator; + ++type_iterator; + } + if (call->arguments.size() != procedure->parameters.size()) + { + add_error<argument_count_error>(procedure->parameters.size(), + call->arguments.size(), call->position()); + } + } + } + + void type_analysis_visitor::visit(record_constructor_expression *expression) + { + auto record = resolve_underlying_type(expression->type_decoration).get<record_type>(); + + if (record == nullptr) + { + 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 (!is_assignable_from(field.second, initializer.value().type_decoration)) + { + add_error<type_mismatch_error>( + initializer.value().position(), field.second, + initializer.value().type_decoration); + } + break; + } + } + } + } + + void type_analysis_visitor::visit(array_constructor_expression *expression) + { + auto array = resolve_underlying_type(expression->type_decoration).get<array_type>(); + + if (array == nullptr) + { + 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) + { + add_error<argument_count_error>(array->size, expression->elements.size(), + expression->position()); + return; + } + for (auto element : expression->elements) + { + if (!is_assignable_from(array->base, element->type_decoration)) + { + add_error<type_mismatch_error>( + element->position(), array->base, element->type_decoration); + } + } + } +} |
