/* 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 source_position position, payload_type payload) : error(position), payload(std::move(payload)) { } std::string declaration_error::what() const { return std::visit([](const auto& payload) -> std::string { using T = std::decay_t; if constexpr (std::is_same_v) { return "Type '" + payload.name + "' not declared"; } else if constexpr (std::is_same_v) { return "Local symbol '" + payload.name + "' cannot be exported"; } else if constexpr (std::is_same_v) { return "Symbol '" + payload.name + "' has been already defined"; } }, this->payload); } std::optional> declaration_error::note() const { if (const auto *redef = std::get_if(&payload)) { if (redef->original.has_value() && redef->original->start().available()) { return std::make_pair("previously declared here", *redef->original); } } return std::nullopt; } const_qualifier_error::const_qualifier_error(const source_position position, kind error_kind) : error(position), error_kind(error_kind) { } std::string const_qualifier_error::what() const { switch (error_kind) { case kind::array_position: return "const must be written before the array size, not after"; case kind::duplicate: return "Duplicate 'const' qualifier is not allowed"; default: __builtin_unreachable(); } } member_error::member_error(const source_position position, payload_type payload) : error(position), payload(std::move(payload)) { } std::string member_error::what() const { return std::visit([](const auto& pay) -> std::string { using T = std::decay_t; if constexpr (std::is_same_v) { const type resolved = resolve_underlying_type(pay.composite); const bool is_enum = resolved.get() != nullptr; const bool is_record = resolved.get() != nullptr; if (is_enum || is_record) { std::string message = is_enum ? "Enumeration" : "Record"; if (auto alias = pay.composite.template get()) { message += " '" + alias->name + "'"; } message += " does not have a "; message += is_enum ? "member" : "field"; message += " named '" + pay.name + "'"; return message; } return "Type '" + pay.composite.to_string() + "' does not have a field named '" + pay.name + "'"; } else if constexpr (std::is_same_v) { const type resolved = resolve_underlying_type(pay.aggregate); const bool is_enum = resolved.get() != nullptr; const std::string kind = is_enum ? "member" : "field"; std::string message = is_enum ? "Enumeration" : "Record"; if (auto alias = pay.aggregate.template get()) { message += " '" + alias->name + "'"; } message += " already has a " + kind + " named '" + pay.name + "'"; if (pay.base.has_value()) { message += " (defined in base type '" + *pay.base + "')"; } return message; } }, payload); } std::optional> member_error::note() const { if (const auto *dup = std::get_if(&payload)) { if (dup->original.has_value() && dup->original->start().available()) { return std::make_pair("previously declared here", *dup->original); } } return std::nullopt; } unsupported_trait_type_error::unsupported_trait_type_error(const identifier& trait, type actual) : error(trait.position()), actual(std::move(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; } } name_analysis_visitor::name_analysis_visitor(symbol_bag bag) : bag(std::move(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 (const auto& [parameter_names, parameters_type] : expression.parameters) { parameters_type->accept(this); for (const 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; } std::optional name_analysis_visitor::lookup_pointer_like_field( const std::string& field_name, const type& element_type) { if (field_name == "length") { return type(std::make_shared(lookup_primitive_type("Word"))); } if (field_name == "ptr") { auto pointer = type(std::make_shared(element_type)); return type(std::make_shared(pointer)); } return std::nullopt; } 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) { const 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 range_base = get_range_base_type(resolved_type)) { if (auto field = lookup_pointer_like_field(field_name, range_base)) { return field.value(); } } 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); if (this->current_type.get() != nullptr) { add_error(expression->position(), const_qualifier_error::kind::duplicate); } this->current_type = type(std::make_shared(this->current_type)); } void name_analysis_visitor::visit(array_type_expression *expression) { walking_visitor::visit(expression); if (this->current_type.get() != nullptr) { add_error(expression->position(), const_qualifier_error::kind::array_position); } this->current_type = type(std::make_shared(this->current_type, expression->size)); } void name_analysis_visitor::visit(slice_type_expression *expression) { walking_visitor::visit(expression); this->current_type = type(std::make_shared(this->current_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{ .declaration = std::nullopt, .base_type = composite_type } }); } } std::vector name_analysis_visitor::build_composite_type( const std::vector& fields, std::map& field_names, const type& aggregate) { std::vector result; for (const auto& field : fields) { field.second->accept(this); for (const 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.position(), member_error::duplicate{.name = field_name.name(), .aggregate = aggregate, .original = existing->second.declaration, .base = base_name}); } else { field_names.insert({ field_name.name(), field_origin{ .declaration = field.second->position(), .base_type = type() } }); result.emplace_back(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 { this->current_type = type(); return; } } else { add_error(expression->base.value().position(), declaration_error::undeclared{.name = expression->base.value().name()}); 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(expression->type_name.position(), declaration_error::undeclared{.name = expression->type_name.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(initializer.id().position(), declaration_error::undeclared{.name = initializer.id().name()}); } } } 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(slicing_expression *expression) { walking_visitor::visit(expression); auto resolved_base = resolve_underlying_type(expression->base().type_decoration); if (auto pointer = resolved_base.get()) { expression->type_decoration = type(std::make_shared(pointer->base)); } else if (auto array = resolved_base.get()) { expression->type_decoration = type(std::make_shared(array->base)); } else if (auto slice = resolved_base.get()) { expression->type_decoration = type(slice); } } void name_analysis_visitor::visit(procedure_type_expression *expression) { std::shared_ptr const 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; member_names.reserve(expression->members.size()); for (const auto& member : expression->members) { member_names.emplace_back(member.name()); } std::shared_ptr const result_type = std::make_shared( member_names); std::map seen; const type aggregate(result_type); for (const auto& member : expression->members) { auto existing = seen.find(member.name()); if (existing != seen.end()) { add_error(member.position(), member_error::duplicate{.name = member.name(), .aggregate = aggregate, .original = existing->second, .base = std::nullopt}); } 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(position, declaration_error::redefinition{.name = name, .original = 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; } 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()) { const type resolved = resolve_underlying_type(trait->type_decoration); if (resolved.get() == nullptr && !is_primitive_type(resolved, "Float") && !is_discrete_type(resolved)) { add_error(trait->name, trait->type_decoration); trait->type_decoration = type(); } } } else { add_error(trait->name.position(), declaration_error::undeclared{.name = trait->name.name()}); } } void name_analysis_visitor::visit(binary_expression *expression) { walking_visitor::visit(expression); switch (expression->operation()) { using enum binary_operator; case equals: case not_equals: case less: case greater: case less_equal: case greater_equal: expression->type_decoration = lookup_primitive_type("Bool"); break; case 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 range_base = get_range_base_type(resolved_base)) { expression->type_decoration = range_base; // Elements of a constant array are constant themselves since a static // array is a holistic type. 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().position(), member_error::not_found{.name = expression->field().name(), .composite = 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(for_statement *statement) { statement->range().accept(this); auto resolved_range = resolve_underlying_type(statement->range().type_decoration); const type control_variable_base_type = get_range_base_type(resolved_range); const type control_variable_pointer_type = type(std::make_shared(control_variable_base_type)); this->current_type = type(std::make_shared(control_variable_pointer_type)); statement->symbols = this->bag.enter(); register_variable(statement->control_variable.name(), false, statement->control_variable.position()); if (statement->counter != nullptr) { this->current_type = lookup_primitive_type("Word"); register_variable(statement->counter->name(), false, statement->counter->position()); } for (auto *body_statement : statement->body) { body_statement->accept(this); } this->bag.leave(); } 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(expression->position(), declaration_error::undeclared{.name = expression->name}); } } void name_analysis_visitor::visit(literal *literal) { literal->type_decoration = lookup_primitive_type("Int"); this->current_type = literal->type_decoration; } void name_analysis_visitor::visit(literal *literal) { literal->type_decoration = lookup_primitive_type("Word"); this->current_type = literal->type_decoration; } void name_analysis_visitor::visit(literal *literal) { literal->type_decoration = lookup_primitive_type("Float"); this->current_type = literal->type_decoration; } void name_analysis_visitor::visit(literal *literal) { literal->type_decoration = lookup_primitive_type("Bool"); this->current_type = literal->type_decoration; } 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 = type(std::make_shared( type(std::make_shared(lookup_primitive_type("Char"))))); this->current_type = literal->type_decoration; } declaration_visitor::declaration_visitor() { } 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().position(), declaration_error::redefinition{.name = declaration->identifier.id().name(), .original = 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(variable_identifier.id().position(), declaration_error::local_export{.name = variable_identifier.id().name()}); } } } }