From 44a0c221d51c9aa036b47b218a6caea4424f5996 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Fri, 17 Jul 2026 13:49:20 +0200 Subject: Handle const Pointer as const pointer to const data --- boot/dependency.cc | 3 +- boot/name_analysis.cc | 765 ++++++++++++ boot/semantic.cc | 1251 -------------------- boot/symbol.cc | 2 +- boot/type_check.cc | 573 +++++++++ gcc/Make-lang.in | 3 +- include/elna/boot/name_analysis.h | 153 +++ include/elna/boot/semantic.h | 324 ----- include/elna/boot/type_check.h | 225 ++++ include/elna/gcc/elna-generic.h | 1 - testsuite/compilable/const_pointer_conversion.elna | 14 + .../fail_compilation/assign_const_to_pointer.elna | 7 + .../assign_from_const_pointer.elna | 7 + 13 files changed, 1749 insertions(+), 1579 deletions(-) create mode 100644 boot/name_analysis.cc delete mode 100644 boot/semantic.cc create mode 100644 boot/type_check.cc create mode 100644 include/elna/boot/name_analysis.h delete mode 100644 include/elna/boot/semantic.h create mode 100644 include/elna/boot/type_check.h create mode 100644 testsuite/compilable/const_pointer_conversion.elna create mode 100644 testsuite/fail_compilation/assign_const_to_pointer.elna create mode 100644 testsuite/fail_compilation/assign_from_const_pointer.elna diff --git a/boot/dependency.cc b/boot/dependency.cc index 84fd800..ada0bac 100644 --- a/boot/dependency.cc +++ b/boot/dependency.cc @@ -22,7 +22,8 @@ along with GCC; see the file COPYING3. If not see #include #include "elna/boot/driver.h" -#include "elna/boot/semantic.h" +#include "elna/boot/name_analysis.h" +#include "elna/boot/type_check.h" #include "parser.hh" namespace elna::boot diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc new file mode 100644 index 0000000..cfc4e35 --- /dev/null +++ b/boot/name_analysis.cc @@ -0,0 +1,765 @@ +/* Name 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 +. */ + +#include "elna/boot/name_analysis.h" + +#include + +namespace elna::boot +{ + declaration_error::declaration_error(const kind error_kind, + const boot::identifier& identifier) + : error(identifier.position()), identifier(identifier.name()), error_kind(error_kind) + { + } + + std::string declaration_error::what() const + { + switch (this->error_kind) + { + case kind::undeclared: + return "Type '" + identifier + "' not declared"; + case kind::local_export: + return "Local symbol '" + this->identifier + "' cannot be exported"; + default: + __builtin_unreachable(); + } + } + + redefinition_error::redefinition_error(const boot::identifier& identifier, + std::optional original) + : error(identifier.position()), identifier(identifier.name()), original(original) + { + } + + std::string redefinition_error::what() const + { + return "Symbol '" + identifier + "' has been already defined"; + } + + std::optional> + redefinition_error::note() const + { + if (original.has_value() && original->start().available()) + { + return std::make_pair("previously declared here", *original); + } + return std::nullopt; + } + + // Members of a constant aggregate are constant themselves. + static type qualify_member_type(const type& element, const type& aggregate) + { + if (resolve_aliases(aggregate).get() != nullptr + && resolve_aliases(element).get() == nullptr) + { + return type(std::make_shared(element)); + } + else + { + return element; + } + } + + name_analysis_visitor::name_analysis_visitor(symbol_bag bag) + : error_container(), bag(bag) + { + } + + std::pair> name_analysis_visitor::build_procedure( + procedure_type_expression& expression) + { + procedure_type::return_t result_return; + + if (expression.return_type.no_return) + { + result_return = procedure_type::return_t(std::monostate{}); + } + else if (expression.return_type.proper_type != nullptr) + { + expression.return_type.proper_type->accept(this); + result_return = procedure_type::return_t(this->current_type); + } + else + { + result_return = procedure_type::return_t(); + } + std::pair> result_type{ + procedure_type(result_return), std::vector() + }; + for (auto& [parameter_names, parameters_type] : expression.parameters) + { + parameters_type->accept(this); + for (auto& parameter_name : parameter_names) + { + result_type.first.parameters.push_back(this->current_type); + result_type.second.push_back(parameter_name.name()); + } + } + return result_type; + } + + type name_analysis_visitor::lookup_primitive_type(const std::string& name) + { + return this->bag.lookup(name)->is_type()->symbol; + } + + type name_analysis_visitor::lookup_field(const type& composite_type, const std::string& field_name) + { + type resolved_type = resolve_underlying_type(composite_type); + + if (auto record = resolved_type.get()) + { + for (auto& field : record->fields) + { + if (field.first == field_name) + { + return field.second; + } + } + if (!record->base.empty()) + { + return lookup_field(record->base, field_name); + } + } + else if (auto primitive = resolved_type.get(); primitive != nullptr && primitive->identifier == "String") + { + if (field_name == "length") + { + return lookup_primitive_type("Word"); + } + else if (field_name == "ptr") + { + return type(std::make_shared(lookup_primitive_type("Char"))); + } + } + return type(); + } + + void name_analysis_visitor::visit(type_declaration *declaration) + { + walking_visitor::visit(declaration); + auto resolved = this->bag.resolve(declaration->identifier.name(), this->current_type); + auto info = std::make_shared(type(resolved)); + + 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) + { + walking_visitor::visit(expression); + this->current_type = type(std::make_shared(this->current_type)); + } + + void name_analysis_visitor::visit(constant_type_expression *expression) + { + walking_visitor::visit(expression); + this->current_type = type(std::make_shared(this->current_type)); + } + + void name_analysis_visitor::visit(array_type_expression *expression) + { + walking_visitor::visit(expression); + auto result_type = std::make_shared(this->current_type, expression->size); + + this->current_type = type(result_type); + } + + /** + * Collects field names from a record type recursively, base first. + */ + static void collect_field_names(const type& composite_type, + std::map& names) + { + auto record = resolve_underlying_type(composite_type).get(); + if (record == nullptr) + { + return; + } + if (!record->base.empty()) + { + collect_field_names(record->base, names); + } + for (auto& field : record->fields) + { + names.insert({ field.first, field_origin{ std::nullopt, composite_type } }); + } + } + + std::vector name_analysis_visitor::build_composite_type( + const std::vector& fields, + std::map& field_names, + type aggregate) + { + std::vector result; + + for (auto& field : fields) + { + field.second->accept(this); + for (auto& field_name : field.first) + { + auto existing = field_names.find(field_name.name()); + if (existing != field_names.end()) + { + std::optional base_name; + + if (!existing->second.declaration.has_value() + && !existing->second.base_type.empty()) + { + if (auto alias = existing->second.base_type.get()) + { + base_name = alias->name; + } + } + add_error(field_name, aggregate, + existing->second.declaration, base_name); + } + else + { + field_names.insert({ field_name.name(), + field_origin{ field.second->position(), type() } }); + result.push_back(std::make_pair(field_name.name(), this->current_type)); + } + } + } + return result; + } + + void name_analysis_visitor::visit(record_type_expression *expression) + { + std::shared_ptr result_type; + if (expression->base.has_value()) + { + if (auto unresolved_alias = this->bag.declared(expression->base.value().name())) + { + result_type = std::make_shared(type(unresolved_alias)); + } + else if (auto base_symbol = this->bag.lookup(expression->base.value().name())) + { + if (auto base_type_info = base_symbol->is_type()) + { + result_type = std::make_shared(base_type_info->symbol); + } + else + { + 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(proc->symbol)); + } + add_error(actual, expression->position()); + this->current_type = type(); + return; + } + } + else + { + add_error(declaration_error::kind::undeclared, + expression->base.value()); + this->current_type = type(); + return; + } + } + else + { + result_type = std::make_shared(); + } + + std::map field_names; + collect_field_names(result_type->base, 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.name())) + { + if (auto type_info = type_symbol->is_type()) + { + expression->type_decoration = type_info->symbol; + } + } + else + { + add_error(declaration_error::kind::undeclared, + expression->type_name); + } + for (const field_initializer& initializer : expression->field_initializers) + { + initializer.value().accept(this); + if (!expression->type_decoration.empty() + && lookup_field(expression->type_decoration, initializer.name()).empty()) + { + add_error(declaration_error::kind::undeclared, + initializer.id()); + } + } + } + + void name_analysis_visitor::visit(array_constructor_expression *expression) + { + expression->m_element_type->accept(this); + auto element_type = this->current_type; + for (auto element : expression->elements) + { + element->accept(this); + } + expression->type_decoration = type(std::make_shared(element_type, expression->size)); + } + + void name_analysis_visitor::visit(procedure_type_expression *expression) + { + std::shared_ptr result_type = + std::make_shared(std::move(build_procedure(*expression).first)); + + this->current_type = type(result_type); + } + + void name_analysis_visitor::visit(enumeration_type_expression *expression) + { + std::vector member_names; + for (auto& member : expression->members) + { + member_names.emplace_back(member.name()); + } + std::shared_ptr result_type = std::make_shared( + member_names); + std::map seen; + type aggregate(result_type); + + for (auto& member : expression->members) + { + auto existing = seen.find(member.name()); + if (existing != seen.end()) + { + add_error(member, aggregate, existing->second); + } + else + { + seen.insert({ member.name(), member.position() }); + } + } + this->current_type = type(result_type); + } + + std::shared_ptr name_analysis_visitor::register_variable(const std::string& name, + const bool is_extern, const source_position position) + { + auto variable_symbol = std::make_shared(this->current_type, is_extern); + variable_symbol->position.emplace(position); + + if (!this->bag.enter(name, variable_symbol)) + { + auto original = this->bag.lookup(name); + add_error(boot::identifier(name, position), + original->position); + } + return variable_symbol; + } + + void name_analysis_visitor::visit(variable_declaration *declaration) + { + declaration->variable_type().accept(this); + auto variable_type = this->current_type; + if (declaration->initializer != nullptr) + { + declaration->initializer->accept(this); + this->current_type = variable_type; + } + for (const identifier_definition& variable_identifier : declaration->identifiers) + { + auto variable_symbol = register_variable(variable_identifier.name(), declaration->is_extern, + declaration->position()); + variable_symbol->exported = variable_identifier.exported(); + } + } + + + void name_analysis_visitor::visit(procedure_declaration *declaration) + { + std::shared_ptr info; + auto [heading, parameter_names] = build_procedure(declaration->heading()); + + if (declaration->body.has_value()) + { + info = std::make_shared(heading, std::move(parameter_names), this->bag.enter()); + auto name_iterator = std::cbegin(info->names); + auto type_iterator = std::cbegin(heading.parameters); + + while (name_iterator != std::cend(info->names) && type_iterator != std::cend(heading.parameters)) + { + this->current_type = *type_iterator; + auto variable_symbol = register_variable(*name_iterator, false, declaration->heading().position()); + variable_symbol->exported = false; + + ++name_iterator; + ++type_iterator; + } + for (variable_declaration *const variable : declaration->body.value().variables) + { + variable->accept(this); + } + for (statement *const statement : declaration->body.value().entry_point) + { + statement->accept(this); + } + if (declaration->body.value().return_expression != nullptr) + { + declaration->body.value().return_expression->accept(this); + } + this->bag.leave(); + } + else + { + info = std::make_shared(heading, std::move(parameter_names)); + } + 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) + { + call->callable().accept(this); + if (auto procedure = call->callable().type_decoration.get()) + { + call->type_decoration = procedure->return_type.proper_type; + } + else if (call->callable().is_named() != nullptr) + { + call->type_decoration = this->current_type; + } + for (expression *const argument : call->arguments) + { + argument->accept(this); + } + } + + void name_analysis_visitor::visit(unit *unit) + { + for (type_declaration *const type : unit->types) + { + type->accept(this); + } + for (variable_declaration *const variable : unit->variables) + { + variable->accept(this); + } + for (procedure_declaration *const procedure : unit->procedures) + { + procedure->accept(this); + } + if (unit->has_body()) + { + this->bag.enter(); + auto variable_type = lookup_primitive_type("Int"); + this->bag.enter("count", std::make_shared(variable_type, false)); + + variable_type = lookup_primitive_type("Char"); + variable_type = type(std::make_shared(variable_type)); + variable_type = type(std::make_shared(variable_type)); + this->bag.enter("parameters", std::make_shared(variable_type, false)); + + for (statement *const statement : unit->entry_point) + { + statement->accept(this); + } + this->bag.leave(); + } + } + + void name_analysis_visitor::visit(traits_expression *trait) + { + if (!trait->arguments.empty()) + { + trait->arguments.front()->accept(this); + trait->types.push_back(this->current_type); + } + + if (trait->name == "size" || trait->name == "alignment" || trait->name == "offset") + { + trait->type_decoration = lookup_primitive_type("Word"); + } + else if (trait->name == "min" || trait->name == "max") + { + trait->type_decoration = trait->types.empty() ? type() : trait->types.front(); + + if (!trait->type_decoration.empty()) + { + type resolved = resolve_underlying_type(trait->type_decoration); + bool is_enum = resolved.get() != nullptr; + bool is_integral = false; + + if (auto prim = resolved.get()) + { + is_integral = prim->identifier == "Int" || prim->identifier == "Word" + || prim->identifier == "Bool" || prim->identifier == "Char"; + } + if (!is_enum && !is_integral) + { + add_error(trait->name, + trait->type_decoration); + trait->type_decoration = type(); + } + } + } + else + { + add_error(declaration_error::kind::undeclared, + trait->name); + } + } + + void name_analysis_visitor::visit(binary_expression *expression) + { + walking_visitor::visit(expression); + + switch (expression->operation()) + { + case binary_operator::equals: + case binary_operator::not_equals: + case binary_operator::less: + case binary_operator::greater: + case binary_operator::less_equal: + case binary_operator::greater_equal: + expression->type_decoration = lookup_primitive_type("Bool"); + break; + case binary_operator::subtraction: + if (expression->lhs().type_decoration.get() + && expression->rhs().type_decoration.get()) + { + expression->type_decoration = lookup_primitive_type("Int"); + } + else + { + expression->type_decoration = expression->lhs().type_decoration; + } + break; + default: + expression->type_decoration = expression->lhs().type_decoration; + break; + } + } + + void name_analysis_visitor::visit(unary_expression *expression) + { + walking_visitor::visit(expression); + + if (expression->operation() == unary_operator::reference) + { + expression->type_decoration = this->current_type + = type(std::make_shared(expression->operand().type_decoration)); + } + else + { + expression->type_decoration = expression->operand().type_decoration; + } + } + + void name_analysis_visitor::visit(array_access_expression *expression) + { + walking_visitor::visit(expression); + auto resolved_base = resolve_underlying_type(expression->base().type_decoration); + + if (auto array = resolved_base.get()) + { + expression->type_decoration = array->base; + } + else if (resolved_base == lookup_primitive_type("String")) + { + expression->type_decoration = lookup_primitive_type("Char"); + } + // Elements of a constant array are constant themselves since a static + // array is a holistic type. + if (!expression->type_decoration.empty()) + { + expression->type_decoration = qualify_member_type(expression->type_decoration, + expression->base().type_decoration); + } + } + + void name_analysis_visitor::visit(field_access_expression *expression) + { + walking_visitor::visit(expression); + 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(expression->field(), expression->base().type_decoration); + } + else + { + expression->type_decoration = qualify_member_type(expression->type_decoration, + expression->base().type_decoration); + } + } + + void name_analysis_visitor::visit(dereference_expression *expression) + { + walking_visitor::visit(expression); + + if (auto pointer = resolve_underlying_type(expression->base().type_decoration).get()) + { + expression->type_decoration = pointer->base; + } + } + + void name_analysis_visitor::visit(cast_expression *expression) + { + walking_visitor::visit(expression); + expression->type_decoration = this->current_type; + } + + void name_analysis_visitor::visit(named_expression *expression) + { + this->current_type = type(); + + if (auto unresolved_alias = this->bag.declared(expression->name)) + { + this->current_type = type(unresolved_alias); + } + else if (auto from_symbol_table = this->bag.lookup(expression->name)) + { + if (auto type_symbol = from_symbol_table->is_type()) + { + this->current_type = type_symbol->symbol; + } + else if (auto variable_symbol = from_symbol_table->is_variable()) + { + expression->type_decoration = variable_symbol->symbol; + } + else if (auto procedure_symbol = from_symbol_table->is_procedure()) + { + expression->type_decoration = type(std::make_shared(procedure_symbol->symbol)); + } + } + else + { + add_error(declaration_error::kind::undeclared, + boot::identifier(expression->name, expression->position())); + } + } + + void name_analysis_visitor::visit(literal *literal) + { + literal->type_decoration = lookup_primitive_type("Int"); + } + + void name_analysis_visitor::visit(literal *literal) + { + literal->type_decoration = lookup_primitive_type("Word"); + } + + void name_analysis_visitor::visit(literal *literal) + { + literal->type_decoration = lookup_primitive_type("Float"); + } + + void name_analysis_visitor::visit(literal *literal) + { + literal->type_decoration = lookup_primitive_type("Bool"); + } + + void name_analysis_visitor::visit(literal *literal) + { + literal->type_decoration = lookup_primitive_type("Char"); + } + + void name_analysis_visitor::visit(literal *literal) + { + literal->type_decoration = lookup_primitive_type("Pointer"); + } + + void name_analysis_visitor::visit(literal *literal) + { + literal->type_decoration = lookup_primitive_type("String"); + } + + declaration_visitor::declaration_visitor() + : error_container() + { + } + + void declaration_visitor::visit(import_declaration *) + { + } + + void declaration_visitor::visit(unit *unit) + { + for (import_declaration *const _import : unit->imports) + { + _import->accept(this); + } + for (type_declaration *const type : unit->types) + { + type->accept(this); + } + for (procedure_declaration *const procedure : unit->procedures) + { + procedure->accept(this); + } + } + + void declaration_visitor::visit(type_declaration *declaration) + { + const std::string& type_identifier = declaration->identifier.name(); + + if (!this->unresolved.insert({ type_identifier, std::make_shared(type_identifier) }).second) + { + add_error(declaration->identifier.id(), + declaration->position()); + } + } + + void declaration_visitor::visit(procedure_declaration *declaration) + { + if (!declaration->body.has_value()) + { + return; + } + for (variable_declaration *const variable : declaration->body.value().variables) + { + variable->accept(this); + } + } + + void declaration_visitor::visit(variable_declaration *declaration) + { + for (const identifier_definition& variable_identifier : declaration->identifiers) + { + if (variable_identifier.exported()) + { + add_error(declaration_error::kind::local_export, + variable_identifier.id()); + } + } + } +} diff --git a/boot/semantic.cc b/boot/semantic.cc deleted file mode 100644 index c987cfd..0000000 --- a/boot/semantic.cc +++ /dev/null @@ -1,1251 +0,0 @@ -/* Name 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 -. */ - -#include "elna/boot/semantic.h" - -#include - -namespace elna::boot -{ - declaration_error::declaration_error(const kind error_kind, - const boot::identifier& identifier) - : error(identifier.position()), identifier(identifier.name()), error_kind(error_kind) - { - } - - std::string declaration_error::what() const - { - switch (this->error_kind) - { - case kind::undeclared: - return "Type '" + identifier + "' not declared"; - case kind::local_export: - return "Local symbol '" + this->identifier + "' cannot be exported"; - default: - __builtin_unreachable(); - } - } - - redefinition_error::redefinition_error(const boot::identifier& identifier, - std::optional original) - : error(identifier.position()), identifier(identifier.name()), original(original) - { - } - - std::string redefinition_error::what() const - { - return "Symbol '" + identifier + "' has been already defined"; - } - - std::optional> - redefinition_error::note() const - { - if (original.has_value() && original->start().available()) - { - 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() + "'"; - } - - 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() != nullptr; - bool is_record = resolved.get() != nullptr; - - if (is_enum || is_record) - { - std::string message = is_enum ? "Enumeration" : "Record"; - - if (auto alias = composite_type.get()) - { - 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 original, - std::optional 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() != nullptr; - std::string kind = is_enum ? "member" : "field"; - std::string message = is_enum ? "Enumeration" : "Record"; - - if (auto alias = aggregate.get()) - { - 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> 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& 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 + "'"; - } - - // Members of a constant aggregate are constant themselves. - static type qualify_member_type(const type& element, const type& aggregate) - { - if (resolve_aliases(aggregate).get() != nullptr - && resolve_aliases(element).get() == nullptr) - { - return type(std::make_shared(element)); - } - else - { - return element; - } - } - - /* - * 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() != nullptr) - { - return true; - } - else if (auto record = referent.get()) - { - 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()) - { - return contains_constant_member(array->base); - } - return false; - } - - bool type_analysis_visitor::check_unresolved_symbol(std::shared_ptr alias, - std::vector& 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()) - { - return check_unresolved_symbol(another_alias, alias_path); - } - return true; - } - - bool type_analysis_visitor::is_base_of(const std::shared_ptr& base, - const std::shared_ptr& derived) - { - if (derived != nullptr) - { - if (auto current_record = resolve_underlying_type(derived->base).get()) - { - return current_record == base || is_base_of(base, current_record); - } - } - return false; - } - - bool type_analysis_visitor::is_assignable_from(const type& assignee, const type& assignment) - { - type resolved_assignee = resolve_underlying_type(assignee); - type resolved_assignment = resolve_underlying_type(assignment); - - if (resolved_assignee == resolved_assignment - || (is_primitive_type(resolved_assignee, "Pointer") && is_any_pointer_type(resolved_assignment)) - || (is_primitive_type(resolved_assignment, "Pointer") && is_any_pointer_type(resolved_assignee))) - { - return true; - } - std::shared_ptr assignee_pointer = resolved_assignee.get(); - std::shared_ptr assignment_pointer = resolved_assignment.get(); - - if (assignee_pointer == nullptr || assignment_pointer == nullptr) - { - return false; - } - auto assignee_pointee_const = resolve_aliases(assignee_pointer->base).get(); - auto assignment_pointee_const = resolve_aliases(assignment_pointer->base).get(); - - // Constness can be added at the first indirection level, but not removed. - if (assignee_pointee_const != nullptr - && assignee_pointee_const->unqualified == assignment_pointer->base) - { - return true; - } - if (assignment_pointee_const != nullptr && assignee_pointee_const == nullptr) - { - return false; - } - // A pointer to a record can be assigned to a pointer to its base type. - std::shared_ptr assignee_record = - resolve_underlying_type(assignee_pointer->base).get(); - - return assignee_record != nullptr - && is_base_of(assignee_record, resolve_underlying_type(assignment_pointer->base).get()); - } - - 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( - return_expr->position(), return_type, return_expr->type_decoration); - } - } - else - { - add_error(declaration->identifier.name(), - return_expr->position(), return_expr->type_decoration); - } - } - else if (declaration->heading().return_type.proper_type != nullptr) - { - add_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(statement->position(), - statement->lvalue().type_decoration); - } - else if (!is_assignable_from(statement->lvalue().type_decoration, statement->rvalue().type_decoration)) - { - add_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( - 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( - case_label->position(), condition_type, case_label->type_decoration); - } - } - } - } - - void type_analysis_visitor::visit(type_declaration *declaration) - { - std::vector alias_path; - auto unresolved_type = this->bag.lookup(declaration->identifier.name())->is_type()->symbol.get(); - - if (!check_unresolved_symbol(unresolved_type, alias_path)) - { - add_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() == nullptr) - { - add_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()) - { - std::vector::const_iterator argument_iterator = std::cbegin(call->arguments); - std::vector::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( - (*argument_iterator)->position(), *type_iterator, - (*argument_iterator)->type_decoration); - } - ++argument_iterator; - ++type_iterator; - } - if (call->arguments.size() != procedure->parameters.size()) - { - add_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(); - - if (record == nullptr) - { - add_error( - expression->position(), type(std::make_shared()), - 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( - 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(); - - if (array == nullptr) - { - add_error( - expression->position(), type(std::make_shared(type(), 0)), - expression->type_decoration); - return; - } - if (expression->elements.size() > array->size) - { - add_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( - element->position(), array->base, element->type_decoration); - } - } - } - - name_analysis_visitor::name_analysis_visitor(symbol_bag bag) - : error_container(), bag(bag) - { - } - - std::pair> name_analysis_visitor::build_procedure( - procedure_type_expression& expression) - { - procedure_type::return_t result_return; - - if (expression.return_type.no_return) - { - result_return = procedure_type::return_t(std::monostate{}); - } - else if (expression.return_type.proper_type != nullptr) - { - expression.return_type.proper_type->accept(this); - result_return = procedure_type::return_t(this->current_type); - } - else - { - result_return = procedure_type::return_t(); - } - std::pair> result_type{ - procedure_type(result_return), std::vector() - }; - for (auto& [parameter_names, parameters_type] : expression.parameters) - { - parameters_type->accept(this); - for (auto& parameter_name : parameter_names) - { - result_type.first.parameters.push_back(this->current_type); - result_type.second.push_back(parameter_name.name()); - } - } - return result_type; - } - - type name_analysis_visitor::lookup_primitive_type(const std::string& name) - { - return this->bag.lookup(name)->is_type()->symbol; - } - - type name_analysis_visitor::lookup_field(const type& composite_type, const std::string& field_name) - { - type resolved_type = resolve_underlying_type(composite_type); - - if (auto record = resolved_type.get()) - { - for (auto& field : record->fields) - { - if (field.first == field_name) - { - return field.second; - } - } - if (!record->base.empty()) - { - return lookup_field(record->base, field_name); - } - } - else if (auto primitive = resolved_type.get(); primitive != nullptr && primitive->identifier == "String") - { - if (field_name == "length") - { - return lookup_primitive_type("Word"); - } - else if (field_name == "ptr") - { - return type(std::make_shared(lookup_primitive_type("Char"))); - } - } - return type(); - } - - void name_analysis_visitor::visit(type_declaration *declaration) - { - walking_visitor::visit(declaration); - auto resolved = this->bag.resolve(declaration->identifier.name(), this->current_type); - auto info = std::make_shared(type(resolved)); - - 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) - { - walking_visitor::visit(expression); - this->current_type = type(std::make_shared(this->current_type)); - } - - void name_analysis_visitor::visit(constant_type_expression *expression) - { - walking_visitor::visit(expression); - this->current_type = type(std::make_shared(this->current_type)); - } - - void name_analysis_visitor::visit(array_type_expression *expression) - { - walking_visitor::visit(expression); - auto result_type = std::make_shared(this->current_type, expression->size); - - this->current_type = type(result_type); - } - - /** - * Collects field names from a record type recursively, base first. - */ - static void collect_field_names(const type& composite_type, - std::map& names) - { - auto record = resolve_underlying_type(composite_type).get(); - if (record == nullptr) - { - return; - } - if (!record->base.empty()) - { - collect_field_names(record->base, names); - } - for (auto& field : record->fields) - { - names.insert({ field.first, field_origin{ std::nullopt, composite_type } }); - } - } - - std::vector name_analysis_visitor::build_composite_type( - const std::vector& fields, - std::map& field_names, - type aggregate) - { - std::vector result; - - for (auto& field : fields) - { - field.second->accept(this); - for (auto& field_name : field.first) - { - auto existing = field_names.find(field_name.name()); - if (existing != field_names.end()) - { - std::optional base_name; - - if (!existing->second.declaration.has_value() - && !existing->second.base_type.empty()) - { - if (auto alias = existing->second.base_type.get()) - { - base_name = alias->name; - } - } - add_error(field_name, aggregate, - existing->second.declaration, base_name); - } - else - { - field_names.insert({ field_name.name(), - field_origin{ field.second->position(), type() } }); - result.push_back(std::make_pair(field_name.name(), this->current_type)); - } - } - } - return result; - } - - void name_analysis_visitor::visit(record_type_expression *expression) - { - std::shared_ptr result_type; - if (expression->base.has_value()) - { - if (auto unresolved_alias = this->bag.declared(expression->base.value().name())) - { - result_type = std::make_shared(type(unresolved_alias)); - } - else if (auto base_symbol = this->bag.lookup(expression->base.value().name())) - { - if (auto base_type_info = base_symbol->is_type()) - { - result_type = std::make_shared(base_type_info->symbol); - } - else - { - 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(proc->symbol)); - } - add_error(actual, expression->position()); - this->current_type = type(); - return; - } - } - else - { - add_error(declaration_error::kind::undeclared, - expression->base.value()); - this->current_type = type(); - return; - } - } - else - { - result_type = std::make_shared(); - } - - std::map field_names; - collect_field_names(result_type->base, 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.name())) - { - if (auto type_info = type_symbol->is_type()) - { - expression->type_decoration = type_info->symbol; - } - } - else - { - add_error(declaration_error::kind::undeclared, - expression->type_name); - } - for (const field_initializer& initializer : expression->field_initializers) - { - initializer.value().accept(this); - if (!expression->type_decoration.empty() - && lookup_field(expression->type_decoration, initializer.name()).empty()) - { - add_error(declaration_error::kind::undeclared, - initializer.id()); - } - } - } - - void name_analysis_visitor::visit(array_constructor_expression *expression) - { - expression->m_element_type->accept(this); - auto element_type = this->current_type; - for (auto element : expression->elements) - { - element->accept(this); - } - expression->type_decoration = type(std::make_shared(element_type, expression->size)); - } - - void name_analysis_visitor::visit(procedure_type_expression *expression) - { - std::shared_ptr result_type = - std::make_shared(std::move(build_procedure(*expression).first)); - - this->current_type = type(result_type); - } - - void name_analysis_visitor::visit(enumeration_type_expression *expression) - { - std::vector member_names; - for (auto& member : expression->members) - { - member_names.emplace_back(member.name()); - } - std::shared_ptr result_type = std::make_shared( - member_names); - std::map seen; - type aggregate(result_type); - - for (auto& member : expression->members) - { - auto existing = seen.find(member.name()); - if (existing != seen.end()) - { - add_error(member, aggregate, existing->second); - } - else - { - seen.insert({ member.name(), member.position() }); - } - } - this->current_type = type(result_type); - } - - std::shared_ptr name_analysis_visitor::register_variable(const std::string& name, - const bool is_extern, const source_position position) - { - auto variable_symbol = std::make_shared(this->current_type, is_extern); - variable_symbol->position.emplace(position); - - if (!this->bag.enter(name, variable_symbol)) - { - auto original = this->bag.lookup(name); - add_error(boot::identifier(name, position), - original->position); - } - return variable_symbol; - } - - void name_analysis_visitor::visit(variable_declaration *declaration) - { - declaration->variable_type().accept(this); - auto variable_type = this->current_type; - if (declaration->initializer != nullptr) - { - declaration->initializer->accept(this); - this->current_type = variable_type; - } - for (const identifier_definition& variable_identifier : declaration->identifiers) - { - auto variable_symbol = register_variable(variable_identifier.name(), declaration->is_extern, - declaration->position()); - variable_symbol->exported = variable_identifier.exported(); - } - } - - - void name_analysis_visitor::visit(procedure_declaration *declaration) - { - std::shared_ptr info; - auto [heading, parameter_names] = build_procedure(declaration->heading()); - - if (declaration->body.has_value()) - { - info = std::make_shared(heading, std::move(parameter_names), this->bag.enter()); - auto name_iterator = std::cbegin(info->names); - auto type_iterator = std::cbegin(heading.parameters); - - while (name_iterator != std::cend(info->names) && type_iterator != std::cend(heading.parameters)) - { - this->current_type = *type_iterator; - auto variable_symbol = register_variable(*name_iterator, false, declaration->heading().position()); - variable_symbol->exported = false; - - ++name_iterator; - ++type_iterator; - } - for (variable_declaration *const variable : declaration->body.value().variables) - { - variable->accept(this); - } - for (statement *const statement : declaration->body.value().entry_point) - { - statement->accept(this); - } - if (declaration->body.value().return_expression != nullptr) - { - declaration->body.value().return_expression->accept(this); - } - this->bag.leave(); - } - else - { - info = std::make_shared(heading, std::move(parameter_names)); - } - 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) - { - call->callable().accept(this); - if (auto procedure = call->callable().type_decoration.get()) - { - call->type_decoration = procedure->return_type.proper_type; - } - else if (call->callable().is_named() != nullptr) - { - call->type_decoration = this->current_type; - } - for (expression *const argument : call->arguments) - { - argument->accept(this); - } - } - - void name_analysis_visitor::visit(unit *unit) - { - for (type_declaration *const type : unit->types) - { - type->accept(this); - } - for (variable_declaration *const variable : unit->variables) - { - variable->accept(this); - } - for (procedure_declaration *const procedure : unit->procedures) - { - procedure->accept(this); - } - if (unit->has_body()) - { - this->bag.enter(); - auto variable_type = lookup_primitive_type("Int"); - this->bag.enter("count", std::make_shared(variable_type, false)); - - variable_type = lookup_primitive_type("Char"); - variable_type = type(std::make_shared(variable_type)); - variable_type = type(std::make_shared(variable_type)); - this->bag.enter("parameters", std::make_shared(variable_type, false)); - - for (statement *const statement : unit->entry_point) - { - statement->accept(this); - } - this->bag.leave(); - } - } - - void name_analysis_visitor::visit(traits_expression *trait) - { - if (!trait->arguments.empty()) - { - trait->arguments.front()->accept(this); - trait->types.push_back(this->current_type); - } - - if (trait->name == "size" || trait->name == "alignment" || trait->name == "offset") - { - trait->type_decoration = lookup_primitive_type("Word"); - } - else if (trait->name == "min" || trait->name == "max") - { - trait->type_decoration = trait->types.empty() ? type() : trait->types.front(); - - if (!trait->type_decoration.empty()) - { - type resolved = resolve_underlying_type(trait->type_decoration); - bool is_enum = resolved.get() != nullptr; - bool is_integral = false; - - if (auto prim = resolved.get()) - { - is_integral = prim->identifier == "Int" || prim->identifier == "Word" - || prim->identifier == "Bool" || prim->identifier == "Char"; - } - if (!is_enum && !is_integral) - { - add_error(trait->name, - trait->type_decoration); - trait->type_decoration = type(); - } - } - } - else - { - add_error(declaration_error::kind::undeclared, - trait->name); - } - } - - void name_analysis_visitor::visit(binary_expression *expression) - { - walking_visitor::visit(expression); - - switch (expression->operation()) - { - case binary_operator::equals: - case binary_operator::not_equals: - case binary_operator::less: - case binary_operator::greater: - case binary_operator::less_equal: - case binary_operator::greater_equal: - expression->type_decoration = lookup_primitive_type("Bool"); - break; - case binary_operator::subtraction: - if (expression->lhs().type_decoration.get() - && expression->rhs().type_decoration.get()) - { - expression->type_decoration = lookup_primitive_type("Int"); - } - else - { - expression->type_decoration = expression->lhs().type_decoration; - } - break; - default: - expression->type_decoration = expression->lhs().type_decoration; - break; - } - } - - void name_analysis_visitor::visit(unary_expression *expression) - { - walking_visitor::visit(expression); - - if (expression->operation() == unary_operator::reference) - { - expression->type_decoration = this->current_type - = type(std::make_shared(expression->operand().type_decoration)); - } - else - { - expression->type_decoration = expression->operand().type_decoration; - } - } - - void name_analysis_visitor::visit(array_access_expression *expression) - { - walking_visitor::visit(expression); - auto resolved_base = resolve_underlying_type(expression->base().type_decoration); - - if (auto array = resolved_base.get()) - { - expression->type_decoration = array->base; - } - else if (resolved_base == lookup_primitive_type("String")) - { - expression->type_decoration = lookup_primitive_type("Char"); - } - // Elements of a constant array are constant themselves since a static - // array is a holistic type. - if (!expression->type_decoration.empty()) - { - expression->type_decoration = qualify_member_type(expression->type_decoration, - expression->base().type_decoration); - } - } - - void name_analysis_visitor::visit(field_access_expression *expression) - { - walking_visitor::visit(expression); - 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(expression->field(), expression->base().type_decoration); - } - else - { - expression->type_decoration = qualify_member_type(expression->type_decoration, - expression->base().type_decoration); - } - } - - void name_analysis_visitor::visit(dereference_expression *expression) - { - walking_visitor::visit(expression); - - if (auto pointer = resolve_underlying_type(expression->base().type_decoration).get()) - { - expression->type_decoration = pointer->base; - } - } - - void name_analysis_visitor::visit(cast_expression *expression) - { - walking_visitor::visit(expression); - expression->type_decoration = this->current_type; - } - - void name_analysis_visitor::visit(named_expression *expression) - { - this->current_type = type(); - - if (auto unresolved_alias = this->bag.declared(expression->name)) - { - this->current_type = type(unresolved_alias); - } - else if (auto from_symbol_table = this->bag.lookup(expression->name)) - { - if (auto type_symbol = from_symbol_table->is_type()) - { - this->current_type = type_symbol->symbol; - } - else if (auto variable_symbol = from_symbol_table->is_variable()) - { - expression->type_decoration = variable_symbol->symbol; - } - else if (auto procedure_symbol = from_symbol_table->is_procedure()) - { - expression->type_decoration = type(std::make_shared(procedure_symbol->symbol)); - } - } - else - { - add_error(declaration_error::kind::undeclared, - boot::identifier(expression->name, expression->position())); - } - } - - void name_analysis_visitor::visit(literal *literal) - { - literal->type_decoration = lookup_primitive_type("Int"); - } - - void name_analysis_visitor::visit(literal *literal) - { - literal->type_decoration = lookup_primitive_type("Word"); - } - - void name_analysis_visitor::visit(literal *literal) - { - literal->type_decoration = lookup_primitive_type("Float"); - } - - void name_analysis_visitor::visit(literal *literal) - { - literal->type_decoration = lookup_primitive_type("Bool"); - } - - void name_analysis_visitor::visit(literal *literal) - { - literal->type_decoration = lookup_primitive_type("Char"); - } - - void name_analysis_visitor::visit(literal *literal) - { - literal->type_decoration = lookup_primitive_type("Pointer"); - } - - void name_analysis_visitor::visit(literal *literal) - { - literal->type_decoration = lookup_primitive_type("String"); - } - - declaration_visitor::declaration_visitor() - : error_container() - { - } - - void declaration_visitor::visit(import_declaration *) - { - } - - void declaration_visitor::visit(unit *unit) - { - for (import_declaration *const _import : unit->imports) - { - _import->accept(this); - } - for (type_declaration *const type : unit->types) - { - type->accept(this); - } - for (procedure_declaration *const procedure : unit->procedures) - { - procedure->accept(this); - } - } - - void declaration_visitor::visit(type_declaration *declaration) - { - const std::string& type_identifier = declaration->identifier.name(); - - if (!this->unresolved.insert({ type_identifier, std::make_shared(type_identifier) }).second) - { - add_error(declaration->identifier.id(), - declaration->position()); - } - } - - void declaration_visitor::visit(procedure_declaration *declaration) - { - if (!declaration->body.has_value()) - { - return; - } - for (variable_declaration *const variable : declaration->body.value().variables) - { - variable->accept(this); - } - } - - void declaration_visitor::visit(variable_declaration *declaration) - { - for (const identifier_definition& variable_identifier : declaration->identifiers) - { - if (variable_identifier.exported()) - { - add_error(declaration_error::kind::local_export, - variable_identifier.id()); - } - } - } -} diff --git a/boot/symbol.cc b/boot/symbol.cc index 9985489..bd6ec49 100644 --- a/boot/symbol.cc +++ b/boot/symbol.cc @@ -583,6 +583,6 @@ namespace elna::boot { return checked.get() != nullptr || checked.get() != nullptr - || is_primitive_type(checked, "Pointer"); + || is_primitive_type(resolve_underlying_type(checked), "Pointer"); } } 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 +. */ + +#include "elna/boot/type_check.h" + +#include + +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() != nullptr; + bool is_record = resolved.get() != nullptr; + + if (is_enum || is_record) + { + std::string message = is_enum ? "Enumeration" : "Record"; + + if (auto alias = composite_type.get()) + { + 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 original, + std::optional 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() != nullptr; + std::string kind = is_enum ? "member" : "field"; + std::string message = is_enum ? "Enumeration" : "Record"; + + if (auto alias = aggregate.get()) + { + 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> 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& 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() != nullptr) + { + return true; + } + else if (auto record = referent.get()) + { + 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()) + { + return contains_constant_member(array->base); + } + return false; + } + + bool type_analysis_visitor::check_unresolved_symbol(std::shared_ptr alias, + std::vector& 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()) + { + 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& base, + const std::shared_ptr& derived) + { + if (derived != nullptr) + { + if (auto current_record = resolve_underlying_type(derived->base).get()) + { + 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(); + ptr != nullptr && resolve_aliases(ptr->base).get() != nullptr) + { + return verdict::reject; + } + if (auto const_assign = ctx.aliased_assignment.get(); + 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(); + auto assignment_ptr = ctx.resolved_assignment.get(); + + if (assignee_ptr == nullptr || assignment_ptr == nullptr) + { + return verdict::reject; + } + auto assignee_pointee_const = resolve_aliases(assignee_ptr->base).get(); + auto assignment_pointee_const = resolve_aliases(assignment_ptr->base).get(); + + // 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()) + { + return is_base_of(assignee_record, + resolve_underlying_type(assignment_ptr->base).get()) + ? 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( + return_expr->position(), return_type, return_expr->type_decoration); + } + } + else + { + add_error(declaration->identifier.name(), + return_expr->position(), return_expr->type_decoration); + } + } + else if (declaration->heading().return_type.proper_type != nullptr) + { + add_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(statement->position(), + statement->lvalue().type_decoration); + } + else if (!is_assignable_from(statement->lvalue().type_decoration, statement->rvalue().type_decoration)) + { + add_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( + 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( + case_label->position(), condition_type, case_label->type_decoration); + } + } + } + } + + void type_analysis_visitor::visit(type_declaration *declaration) + { + std::vector alias_path; + auto unresolved_type = this->bag.lookup(declaration->identifier.name())->is_type()->symbol.get(); + + if (!check_unresolved_symbol(unresolved_type, alias_path)) + { + add_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() == nullptr) + { + add_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()) + { + std::vector::const_iterator argument_iterator = std::cbegin(call->arguments); + std::vector::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( + (*argument_iterator)->position(), *type_iterator, + (*argument_iterator)->type_decoration); + } + ++argument_iterator; + ++type_iterator; + } + if (call->arguments.size() != procedure->parameters.size()) + { + add_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(); + + if (record == nullptr) + { + add_error( + expression->position(), type(std::make_shared()), + 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( + 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(); + + if (array == nullptr) + { + add_error( + expression->position(), type(std::make_shared(type(), 0)), + expression->type_decoration); + return; + } + if (expression->elements.size() > array->size) + { + add_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( + element->position(), array->base, element->type_decoration); + } + } + } +} diff --git a/gcc/Make-lang.in b/gcc/Make-lang.in index cb57798..e6d6f2d 100644 --- a/gcc/Make-lang.in +++ b/gcc/Make-lang.in @@ -52,7 +52,8 @@ elna_OBJS = \ elna/driver.o \ elna/lexer.o \ elna/parser.o \ - elna/semantic.o \ + elna/name_analysis.o \ + elna/type_check.o \ elna/symbol.o \ elna/result.o \ $(END) diff --git a/include/elna/boot/name_analysis.h b/include/elna/boot/name_analysis.h new file mode 100644 index 0000000..4498d0c --- /dev/null +++ b/include/elna/boot/name_analysis.h @@ -0,0 +1,153 @@ +/* Name 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 +. */ + +#pragma once + +#include +#include +#include + +#include "elna/boot/ast.h" +#include "elna/boot/result.h" +#include "elna/boot/symbol.h" +#include "elna/boot/type_check.h" + +namespace elna::boot +{ + /** + * Error declaring a symbol. + */ + class declaration_error : public error + { + public: + enum class kind + { + undeclared, + local_export + }; + + private: + std::string identifier; + kind error_kind; + + public: + + declaration_error(const kind error_kind, + const boot::identifier& identifier); + + std::string what() const override; + }; + + /** + * Attempted to redefine a name that is already in use in the + * current scope. + */ + class redefinition_error : public error + { + std::string identifier; + std::optional original; + + public: + redefinition_error(const boot::identifier& identifier, + std::optional original); + + std::string what() const override; + + std::optional> note() const override; + }; + + /** + * Origin of a field in a composite type. + */ + struct field_origin + { + std::optional declaration; + type base_type; + }; + + /** + * Performs name analysis. + */ + class name_analysis_visitor final : public walking_visitor, public error_container + { + type current_type; + + symbol_bag bag; + + std::pair> build_procedure( + procedure_type_expression& expression); + std::vector build_composite_type(const std::vector& fields, + std::map& known_names, + type aggregate); + std::shared_ptr register_variable(const std::string& name, + const bool is_extern, const source_position position); + + type lookup_primitive_type(const std::string& name); + type lookup_field(const type& composite_type, const std::string& field_name); + + public: + name_analysis_visitor(symbol_bag bag); + + void visit(array_type_expression *expression) override; + void visit(pointer_type_expression *expression) override; + void visit(constant_type_expression *expression) override; + void visit(type_declaration *declaration) override; + void visit(record_type_expression *expression) override; + void visit(record_constructor_expression *expression) override; + void visit(array_constructor_expression *expression) override; + void visit(procedure_type_expression *expression) override; + void visit(enumeration_type_expression *expression) override; + + void visit(variable_declaration *declaration) override; + void visit(procedure_declaration *declaration) override; + + void visit(procedure_call *call) override; + void visit(unit *unit) override; + void visit(cast_expression *expression) override; + void visit(traits_expression *trait) override; + void visit(binary_expression *expression) override; + void visit(unary_expression *expression) override; + void visit(named_expression *expression) override; + void visit(array_access_expression *expression) override; + void visit(field_access_expression *expression) override; + void visit(dereference_expression *expression) override; + void visit(literal *literal) override; + void visit(literal *literal) override; + void visit(literal *literal) override; + void visit(literal *literal) override; + void visit(literal *literal) override; + void visit(literal *literal) override; + void visit(literal *literal) override; + }; + + /** + * Collects global declarations without resolving any symbols. + */ + class declaration_visitor final : public empty_visitor, public error_container + { + public: + forward_table unresolved; + + explicit declaration_visitor(); + + void visit(import_declaration *) override; + void visit(unit *unit) override; + void visit(type_declaration *declaration) override; + void visit(procedure_declaration *declaration) override; + void visit(variable_declaration *declaration) override; + }; +} diff --git a/include/elna/boot/semantic.h b/include/elna/boot/semantic.h deleted file mode 100644 index 3d2de10..0000000 --- a/include/elna/boot/semantic.h +++ /dev/null @@ -1,324 +0,0 @@ -/* Name 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 -. */ - -#pragma once - -#include -#include -#include - -#include "elna/boot/ast.h" -#include "elna/boot/result.h" -#include "elna/boot/symbol.h" - -namespace elna::boot -{ - /** - * Error declaring a symbol. - */ - class declaration_error : public error - { - public: - enum class kind - { - undeclared, - local_export - }; - - private: - std::string identifier; - kind error_kind; - - public: - - declaration_error(const kind error_kind, - const boot::identifier& identifier); - - std::string what() const override; - }; - - /** - * Attempted to redefine a name that is already in use in the - * current scope. - */ - class redefinition_error : public error - { - std::string identifier; - std::optional original; - - public: - redefinition_error(const boot::identifier& identifier, - std::optional original); - - std::string what() const override; - - std::optional> note() const override; - }; - - /** - * Expected type does not match the actual type of an expression. - */ - class type_mismatch_error : public error - { - type expected; - type actual; - - public: - type_mismatch_error(const source_position position, - type expected, type actual); - - std::string what() const override; - }; - - /** - * Attempted to assign a value whose type contains constant members. - */ - class constant_assignment_error : public error - { - type assignee; - - public: - constant_assignment_error(const source_position position, type assignee); - - std::string what() const override; - }; - - /** - * Attempted to access a field that does not exist on the given type. - */ - class field_not_found_error : public error - { - std::string field_name; - type composite_type; - - public: - field_not_found_error(const identifier& field_name, - type composite_type); - - std::string what() const override; - }; - - /** - * Field with the same name is already declared in this type. - */ - class duplicate_member_error : public error - { - std::string member_name; - type aggregate; - std::optional original; - std::optional base_name; - - public: - duplicate_member_error(const boot::identifier& member_name, - type aggregate, std::optional original = std::nullopt, - std::optional base_name = std::nullopt); - - std::string what() const override; - - std::optional> note() const override; - }; - - /** - * Cyclic type declaration. - */ - class cyclic_declaration_error : public error - { - std::vector cycle; - - public: - cyclic_declaration_error(const std::vector& cycle, const source_position position); - - std::string what() const override; - }; - - /** - * Procedure with a return type does not return. - */ - class return_error : public error - { - std::string identifier; - type return_type; - - public: - return_error(const std::string& identifier, const source_position position, - type return_type = type()); - - std::string what() const override; - }; - - /** - * Base type of a record is not a record. - */ - class base_type_error : public error - { - type actual; - - public: - base_type_error(type actual, const source_position position); - - std::string what() const override; - }; - - /** - * Argument count in a procedure or record call doesn't match - * the expected number of parameters or fields. - */ - class argument_count_error : public error - { - std::size_t expected; - std::size_t actual; - - public: - argument_count_error(std::size_t expected, std::size_t actual, - const source_position position); - - std::string what() const override; - }; - - /** - * Type passed to a trait like #min or #max does not support - * the trait. - */ - class unsupported_trait_type_error : public error - { - type actual; - std::string trait_name; - - public: - unsupported_trait_type_error(const identifier& trait, type actual); - - std::string what() const override; - }; - - /** - * Checks types. - */ - class type_analysis_visitor final : public walking_visitor, public error_container - { - symbol_bag bag; - std::shared_ptr current_procedure; - - /* - * Whether an expression of type assignment can be assigned to a variable - * of type assignee. - */ - static bool is_assignable_from(const type& assignee, const type& assignment); - /* - * Checks whether derived has base in its parent chain. - * base should not be null, derived can be null. - */ - static bool is_base_of(const std::shared_ptr& base, - const std::shared_ptr& derived); - static bool check_unresolved_symbol(std::shared_ptr alias, - std::vector& path); - - public: - explicit type_analysis_visitor(symbol_bag bag); - - void visit(procedure_declaration *declaration) override; - void visit(unit *unit) override; - void visit(assign_statement *statement) override; - void visit(variable_declaration *declaration) override; - void visit(type_declaration *declaration) override; - void visit(record_type_expression *expression) override; - void visit(procedure_call *call) override; - void visit(case_statement *statement) override; - void visit(record_constructor_expression *expression) override; - void visit(array_constructor_expression *expression) override; - }; - - /** - * Origin of a field in a composite type. - */ - struct field_origin - { - std::optional declaration; - type base_type; - }; - - /** - * Performs name analysis. - */ - class name_analysis_visitor final : public walking_visitor, public error_container - { - type current_type; - - symbol_bag bag; - - std::pair> build_procedure( - procedure_type_expression& expression); - std::vector build_composite_type(const std::vector& fields, - std::map& known_names, - type aggregate); - std::shared_ptr register_variable(const std::string& name, - const bool is_extern, const source_position position); - - type lookup_primitive_type(const std::string& name); - type lookup_field(const type& composite_type, const std::string& field_name); - - public: - name_analysis_visitor(symbol_bag bag); - - void visit(array_type_expression *expression) override; - void visit(pointer_type_expression *expression) override; - void visit(constant_type_expression *expression) override; - void visit(type_declaration *declaration) override; - void visit(record_type_expression *expression) override; - void visit(record_constructor_expression *expression) override; - void visit(array_constructor_expression *expression) override; - void visit(procedure_type_expression *expression) override; - void visit(enumeration_type_expression *expression) override; - - void visit(variable_declaration *declaration) override; - void visit(procedure_declaration *declaration) override; - - void visit(procedure_call *call) override; - void visit(unit *unit) override; - void visit(cast_expression *expression) override; - void visit(traits_expression *trait) override; - void visit(binary_expression *expression) override; - void visit(unary_expression *expression) override; - void visit(named_expression *expression) override; - void visit(array_access_expression *expression) override; - void visit(field_access_expression *expression) override; - void visit(dereference_expression *expression) override; - void visit(literal *literal) override; - void visit(literal *literal) override; - void visit(literal *literal) override; - void visit(literal *literal) override; - void visit(literal *literal) override; - void visit(literal *literal) override; - void visit(literal *literal) override; - }; - - /** - * Collects global declarations without resolving any symbols. - */ - class declaration_visitor final : public empty_visitor, public error_container - { - public: - forward_table unresolved; - - explicit declaration_visitor(); - - void visit(import_declaration *) override; - void visit(unit *unit) override; - void visit(type_declaration *declaration) override; - void visit(procedure_declaration *declaration) override; - void visit(variable_declaration *declaration) override; - }; -} diff --git a/include/elna/boot/type_check.h b/include/elna/boot/type_check.h new file mode 100644 index 0000000..b250f48 --- /dev/null +++ b/include/elna/boot/type_check.h @@ -0,0 +1,225 @@ +/* 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 +. */ + +#pragma once + +#include +#include +#include + +#include "elna/boot/ast.h" +#include "elna/boot/result.h" +#include "elna/boot/symbol.h" + +namespace elna::boot +{ + /** + * Expected type does not match the actual type of an expression. + */ + class type_mismatch_error : public error + { + type expected; + type actual; + + public: + type_mismatch_error(const source_position position, + type expected, type actual); + + std::string what() const override; + }; + + /** + * Attempted to assign a value whose type contains constant members. + */ + class constant_assignment_error : public error + { + type assignee; + + public: + constant_assignment_error(const source_position position, type assignee); + + std::string what() const override; + }; + + /** + * Attempted to access a field that does not exist on the given type. + */ + class field_not_found_error : public error + { + std::string field_name; + type composite_type; + + public: + field_not_found_error(const identifier& field_name, + type composite_type); + + std::string what() const override; + }; + + /** + * Field with the same name is already declared in this type. + */ + class duplicate_member_error : public error + { + std::string member_name; + type aggregate; + std::optional original; + std::optional base_name; + + public: + duplicate_member_error(const boot::identifier& member_name, + type aggregate, std::optional original = std::nullopt, + std::optional base_name = std::nullopt); + + std::string what() const override; + + std::optional> note() const override; + }; + + /** + * Cyclic type declaration. + */ + class cyclic_declaration_error : public error + { + std::vector cycle; + + public: + cyclic_declaration_error(const std::vector& cycle, const source_position position); + + std::string what() const override; + }; + + /** + * Procedure with a return type does not return. + */ + class return_error : public error + { + std::string identifier; + type return_type; + + public: + return_error(const std::string& identifier, const source_position position, + type return_type = type()); + + std::string what() const override; + }; + + /** + * Base type of a record is not a record. + */ + class base_type_error : public error + { + type actual; + + public: + base_type_error(type actual, const source_position position); + + std::string what() const override; + }; + + /** + * Argument count in a procedure or record call doesn't match + * the expected number of parameters or fields. + */ + class argument_count_error : public error + { + std::size_t expected; + std::size_t actual; + + public: + argument_count_error(std::size_t expected, std::size_t actual, + const source_position position); + + std::string what() const override; + }; + + /** + * Type passed to a trait like #min or #max does not support + * the trait. + */ + class unsupported_trait_type_error : public error + { + type actual; + std::string trait_name; + + public: + unsupported_trait_type_error(const identifier& trait, type actual); + + std::string what() const override; + }; + + /** + * Chain of responsibility for type compatibility checks. + * + * Populate \c ctx with pre-resolved types, then call \c run(). + * Each handler inspects the context and returns a verdict — + * the first non-\c pass verdict wins. + */ + struct assign_check + { + enum class verdict { pass, accept, reject }; + + struct context + { + type aliased_assignee; + type aliased_assignment; + type resolved_assignee; + type resolved_assignment; + }; + context ctx; + + verdict guard_const_laundering(); + verdict check_exact_match(); + verdict check_pointer_hatch(); + verdict check_pointer_conversion(); + + bool run(); + }; + + /** + * Checks types: assignment compatibility, const-correctness, + * procedure argument counts, record field validity, + * and type declaration well-formedness. + */ + class type_analysis_visitor final : public walking_visitor, public error_container + { + symbol_bag bag; + std::shared_ptr current_procedure; + + /* + * Whether an expression of type assignment can be assigned to a variable + * of type assignee. + */ + static bool is_assignable_from(const type& assignee, const type& assignment); + static bool check_unresolved_symbol(std::shared_ptr alias, + std::vector& path); + + public: + explicit type_analysis_visitor(symbol_bag bag); + + void visit(procedure_declaration *declaration) override; + void visit(unit *unit) override; + void visit(assign_statement *statement) override; + void visit(variable_declaration *declaration) override; + void visit(type_declaration *declaration) override; + void visit(record_type_expression *expression) override; + void visit(procedure_call *call) override; + void visit(case_statement *statement) override; + void visit(record_constructor_expression *expression) override; + void visit(array_constructor_expression *expression) override; + }; +} diff --git a/include/elna/gcc/elna-generic.h b/include/elna/gcc/elna-generic.h index a1689be..dd5c9ef 100644 --- a/include/elna/gcc/elna-generic.h +++ b/include/elna/gcc/elna-generic.h @@ -20,7 +20,6 @@ along with GCC; see the file COPYING3. If not see #include #include "elna/boot/ast.h" #include "elna/boot/symbol.h" -#include "elna/boot/semantic.h" #include "elna/gcc/elna-tree.h" #include "config.h" diff --git a/testsuite/compilable/const_pointer_conversion.elna b/testsuite/compilable/const_pointer_conversion.elna new file mode 100644 index 0000000..003fbea --- /dev/null +++ b/testsuite/compilable/const_pointer_conversion.elna @@ -0,0 +1,14 @@ +var + x: Int + c: const Int + p: ^Int + pc: ^const Int + v: Pointer + cv: const Pointer := nil + cv2: const Pointer := @x + cv3: const Pointer := @c + +begin + v := p; + v := @x +end. diff --git a/testsuite/fail_compilation/assign_const_to_pointer.elna b/testsuite/fail_compilation/assign_const_to_pointer.elna new file mode 100644 index 0000000..c3d9230 --- /dev/null +++ b/testsuite/fail_compilation/assign_const_to_pointer.elna @@ -0,0 +1,7 @@ +var + c: const Int + p: Pointer + +begin + p := @c (* @Error Expected type 'Pointer', but got '\^const Int' *) +end. diff --git a/testsuite/fail_compilation/assign_from_const_pointer.elna b/testsuite/fail_compilation/assign_from_const_pointer.elna new file mode 100644 index 0000000..164bb42 --- /dev/null +++ b/testsuite/fail_compilation/assign_from_const_pointer.elna @@ -0,0 +1,7 @@ +var + cv: const Pointer + p: Pointer + +begin + p := cv (* @Error Expected type 'Pointer', but got 'const Pointer' *) +end. -- cgit v1.2.3