/* 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 std::string& identifier, const struct position position) : error(position), identifier(identifier), error_kind(error_kind) { } std::string declaration_error::what() const { switch (this->error_kind) { case kind::undeclared: return "Type '" + identifier + "' not declared"; case kind::already: return "Symbol '" + identifier + "' has been already declared"; case kind::local_export: return "Local symbol '" + this->identifier + "' cannot be exported"; default: __builtin_unreachable(); } } type_expectation_error::type_expectation_error(const kind error_kind, const struct position position) : error(position), error_kind(error_kind) { } std::string type_expectation_error::what() const { switch (this->error_kind) { case kind::assignment: return "Assinging an incompatible type"; case kind::result: return "Procedure return type and type of the return expression are not compatible"; case kind::argument: return "Argument type does not match parameter"; default: __builtin_unreachable(); } } field_duplication_error::field_duplication_error(const std::string& field_name, const struct position position) : error(position), field_name(field_name) { } std::string field_duplication_error::what() const { return "Repeated field name '" + field_name + "'"; } cyclic_declaration_error::cyclic_declaration_error(const std::vector& cycle, const struct 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 struct position position) : error(position), identifier(identifier) { } std::string return_error::what() const { return "Procedure '" + this->identifier + "' is expected to return, but does not have a return statement"; } base_type_error::base_type_error(const std::string& base, const struct position position) : error(position), base(base) { } std::string base_type_error::what() const { return "Record base type '" + this->base + "' is not a record."; } argument_count_error::argument_count_error(std::size_t expected, std::size_t actual, const struct 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); } } type_analysis_visitor::type_analysis_visitor(symbol_bag bag) : error_container(), bag(bag) { } void type_analysis_visitor::visit(procedure_declaration *declaration) { this->current_procedure = this->bag.lookup(declaration->identifier.name)->is_procedure(); if (declaration->body.has_value()) { this->bag.enter(this->current_procedure->scope); } walking_visitor::visit(declaration); if (declaration->body.has_value()) { if (declaration->body.value().return_expression != nullptr) { expression *return_expr = declaration->body.value().return_expression; type return_type = this->current_procedure->symbol.return_type.proper_type; if (!return_type.empty()) { if (!is_assignable_from(return_type, return_expr->type_decoration)) { add_error(type_expectation_error::kind::result, return_expr->position()); } } else { add_error(type_expectation_error::kind::result, return_expr->position()); } } 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); if (unit->has_body()) { if (unit->return_expression != nullptr) { type return_type = this->bag.lookup("Int")->is_type()->symbol; if (!is_assignable_from(return_type, unit->return_expression->type_decoration)) { add_error(type_expectation_error::kind::result, unit->return_expression->position()); } } else { add_error("module", unit->position()); } } } void type_analysis_visitor::visit(assign_statement *statement) { walking_visitor::visit(statement); if (!is_assignable_from(statement->lvalue().type_decoration, statement->rvalue().type_decoration)) { add_error(type_expectation_error::kind::assignment, statement->position()); } } void type_analysis_visitor::visit(variable_declaration *declaration) { walking_visitor::visit(declaration); if (declaration->initializer == nullptr) { return; } for (const identifier_definition& variable_identifier : declaration->identifiers) { auto variable_symbol = this->bag.lookup(variable_identifier.name)->is_variable(); if (!is_assignable_from(variable_symbol->symbol, declaration->initializer->type_decoration)) { add_error(type_expectation_error::kind::assignment, declaration->initializer->position()); } } } void type_analysis_visitor::visit(case_statement *statement) { walking_visitor::visit(statement); type condition_type = inner_aliased_type(statement->condition().type_decoration); for (const switch_case& case_block : statement->cases) { for (expression *const case_label : case_block.labels) { if (!is_assignable_from(condition_type, case_label->type_decoration)) { add_error(type_expectation_error::kind::assignment, case_label->position()); } } } } 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; } 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 = inner_aliased_type(this->bag.lookup(expression->base.value())->is_type()->symbol); if (base_type.get() == nullptr) { add_error(expression->base.value(), 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(type_expectation_error::kind::argument, (*argument_iterator)->position()); } ++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 = inner_aliased_type(expression->type_decoration).get(); if (record == nullptr) { add_error(type_expectation_error::kind::result, expression->position()); return; } for (const field_initializer& initializer : expression->field_initializers) { for (const type_field& field : record->fields) { if (field.first == initializer.name) { if (!is_assignable_from(field.second, initializer.value->type_decoration)) { add_error(type_expectation_error::kind::argument, initializer.value->position()); } break; } } } } 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); } } 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::type_of_constant(const constant_info::variant& value) { return std::visit([this](auto&& arg) -> type { using T = std::decay_t; if constexpr (std::is_same_v) { return lookup_primitive_type("Int"); } else if constexpr (std::is_same_v) { return lookup_primitive_type("Word"); } else if constexpr (std::is_same_v) { return lookup_primitive_type("Float"); } else if constexpr (std::is_same_v) { return lookup_primitive_type("Bool"); } else if constexpr (std::is_same_v) { return lookup_primitive_type("Char"); } else if constexpr (std::is_same_v) { return lookup_primitive_type("Pointer"); } else if constexpr (std::is_same_v) { return lookup_primitive_type("String"); } else { static_assert(!sizeof(T*), "Unhandled constant type"); } }, value); } type name_analysis_visitor::lookup_field(const type& composite_type, const std::string& field_name) { type resolved_type = inner_aliased_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; 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(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::set& names) { auto record = inner_aliased_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); } } std::vector name_analysis_visitor::build_composite_type( const std::vector& fields, std::set& field_names) { std::vector result; for (auto& field : fields) { field.second->accept(this); for (auto& field_name : field.first) { if (!field_names.insert(field_name).second) { add_error(field_name, field.second->position()); } else { result.push_back(std::make_pair(field_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())) { result_type = std::make_shared(type(unresolved_alias)); } else if (auto base_symbol = this->bag.lookup(expression->base.value())) { if (auto base_type_info = base_symbol->is_type()) { result_type = std::make_shared(base_type_info->symbol); } else { add_error(expression->base.value(), expression->position()); this->current_type = type(); return; } } else { add_error(declaration_error::kind::undeclared, expression->base.value(), expression->position()); this->current_type = type(); return; } } else { result_type = std::make_shared(); } std::set field_names; collect_field_names(result_type->base, field_names); result_type->fields = build_composite_type(expression->fields, field_names); this->current_type = type(result_type); } void name_analysis_visitor::visit(record_constructor_expression *expression) { if (auto type_symbol = this->bag.lookup(expression->type_name)) { if (auto type_info = type_symbol->is_type()) { expression->type_decoration = type_info->symbol; } } 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.name, expression->position()); } } } 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::shared_ptr result_type = std::make_shared(expression->members); this->current_type = type(result_type); } std::shared_ptr name_analysis_visitor::register_variable(const std::string& name, const bool is_extern, const struct position position) { auto variable_symbol = std::make_shared(this->current_type, is_extern); if (!this->bag.enter(name, variable_symbol)) { add_error(declaration_error::kind::already, name, position); } return variable_symbol; } void name_analysis_visitor::visit(variable_declaration *declaration) { walking_visitor::visit(declaration); 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(constant_declaration *declaration) { walking_visitor::visit(declaration); auto constant_symbol = std::make_shared(this->current_literal); constant_symbol->exported = declaration->identifier.exported; this->bag.enter(declaration->identifier.name, constant_symbol); } 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 (constant_declaration *const constant : declaration->body.value().constants) { constant->accept(this); } 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; 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 (constant_declaration *const constant : unit->constants) { constant->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); } if (unit->return_expression != nullptr) { unit->return_expression->accept(this); } this->bag.leave(); } } void name_analysis_visitor::visit(traits_expression *trait) { if (!trait->parameters.empty()) { trait->parameters.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(); } } 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= inner_aliased_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"); } } void name_analysis_visitor::visit(field_access_expression *expression) { walking_visitor::visit(expression); expression->type_decoration = lookup_field(expression->base().type_decoration, expression->field()); 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; } } void name_analysis_visitor::visit(dereference_expression *expression) { walking_visitor::visit(expression); if (auto pointer = inner_aliased_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 constant_symbol = from_symbol_table->is_constant()) { expression->type_decoration = type_of_constant(constant_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, expression->name, expression->position()); } } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; literal->type_decoration = lookup_primitive_type("Int"); } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; literal->type_decoration = lookup_primitive_type("Word"); } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; literal->type_decoration = lookup_primitive_type("Float"); } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; literal->type_decoration = lookup_primitive_type("Bool"); } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; literal->type_decoration = lookup_primitive_type("Char"); } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; literal->type_decoration = lookup_primitive_type("Pointer"); } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; 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_error::kind::already, declaration->identifier.name, declaration->position()); } } void declaration_visitor::visit(procedure_declaration *declaration) { if (!declaration->body.has_value()) { return; } for (constant_declaration *const constant : declaration->body.value().constants) { constant->accept(this); } 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.name, declaration->position()); } } } void declaration_visitor::visit(constant_declaration *declaration) { if (declaration->identifier.exported) { add_error(declaration_error::kind::local_export, declaration->identifier.name, declaration->position()); } } bool is_base_of(std::shared_ptr base, std::shared_ptr derived) { type current = derived->base; while (!current.empty()) { auto current_record = inner_aliased_type(current).get(); if (current_record == nullptr) { return false; } if (current_record == base) { return true; } current = current_record->base; } return false; } bool are_compatible_pointers(type lhs_type, type rhs_type) { std::shared_ptr lhs_primitive = lhs_type.get(); std::shared_ptr rhs_primitive = rhs_type.get(); bool lhs_is_primitive_pointer = lhs_primitive != nullptr && lhs_primitive->identifier == "Pointer"; bool rhs_is_primitive_pointer = rhs_primitive != nullptr && rhs_primitive->identifier == "Pointer"; return (lhs_is_primitive_pointer && rhs_type.get() != nullptr) || (rhs_is_primitive_pointer && lhs_type.get() != nullptr) || (lhs_is_primitive_pointer && rhs_type.get() != nullptr) || (rhs_is_primitive_pointer && lhs_type.get() != nullptr) || (lhs_is_primitive_pointer && rhs_is_primitive_pointer); } bool is_assignable_from(const type& assignee, const type& assignment) { type resolved_assignee = inner_aliased_type(assignee); type resolved_assignment = inner_aliased_type(assignment); if (resolved_assignee == resolved_assignment) { return true; } // If the types do not match, only some pointers can be assigned. if (are_compatible_pointers(resolved_assignee, resolved_assignment)) { 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; } // A pointer to a record can be assigned to a pointer to its base type. type assignee_pointee = inner_aliased_type(assignee_pointer->base); type assignment_pointee = inner_aliased_type(assignment_pointer->base); std::shared_ptr assignee_record = assignee_pointee.get(); std::shared_ptr assignment_record = assignment_pointee.get(); return assignee_record != nullptr && assignment_record != nullptr && is_base_of(assignee_record, assignment_record); } }