/* 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 #include #include namespace elna::boot { symbol_declaration_error::symbol_declaration_error(const source_position position, const std::string& name, payload_type payload) : diagnostic(position), name(name), payload(std::move(payload)) { } std::string symbol_declaration_error::what() const { return std::visit([this](const auto& payload) -> std::string { using T = std::decay_t; if constexpr (std::is_same_v) { return "Symbol '" + this->name + "' has been already defined"; } else if constexpr (std::is_same_v) { switch (payload) { using enum kind; case undeclared_type: return "Type '" + this->name + "' not declared"; case undeclared_trait: return "Trait '#" + this->name + "' not declared"; case undeclared_symbol: return "Symbol '" + this->name + "' not declared"; case undeclared_label: return "Label '" + this->name + "' not declared"; case not_a_type: return "'" + this->name + "' is not a type"; case not_a_label: return "'" + this->name + "' is not a label"; case break_leaves_defer: return "'break' cannot leave a defer statement"; default: __builtin_unreachable(); } } }, this->payload); } attribute_error::attribute_error(const source_position position, const std::string& name, kind attribute_kind) : diagnostic(position), name(name), m_kind(attribute_kind) { } std::string attribute_error::what() const { switch (this->m_kind) { using enum kind; case unknown: return "Attribute '" + this->name + "' not declared"; case duplicate: return "Attribute '" + this->name + "' specified more than once"; case unsupported: return "Attribute '" + this->name + "' is not supported in this position"; case argument_count: return "Attribute '" + this->name + "' takes exactly one argument"; case not_constant: return "Attribute '" + this->name + "' requires a constant expression"; case invalid_alignment: return "Alignment must be a power of two"; case unexpected_arguments: return "Attribute '" + this->name + "' takes no arguments"; case missing_parameter: return "Attribute '" + this->name + "' requires at least one parameter"; default: __builtin_unreachable(); } } std::optional symbol_declaration_error::note() const { if (std::holds_alternative(this->payload)) { return previous_declaration_note(std::get(this->payload).original, "previously declared here", std::get(this->payload).file); } else { return std::nullopt; } } cyclic_declaration_error::cyclic_declaration_error(const source_position position, const std::vector& cycle) : diagnostic(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 + " -> " + this->cycle.front(); } declaration_format_error::declaration_format_error(const source_position position, payload_type payload) : diagnostic(position), payload(std::move(payload)) { } std::string declaration_format_error::what() const { return std::visit([](const auto& payload) -> std::string { using T = std::decay_t; if constexpr (std::is_same_v) { return "All constants should be initialized"; } else if constexpr (std::is_same_v) { switch (payload) { using enum kind; case array_position: return "const must be written before the array size, not after"; case duplicate: return "Duplicate 'const' qualifier is not allowed"; case module_entry: return "Program entry point should have no or one argument"; default: __builtin_unreachable(); } } }, this->payload); } std::optional declaration_format_error::note() const { if (std::holds_alternative(this->payload)) { return identifier_list_note(std::get(this->payload).identifiers); } else { return std::nullopt; } } member_error::member_error(const source_position position, const std::string& name, const type& composite, payload_type payload) : diagnostic(position), name(name), composite(composite), payload(std::move(payload)) { } std::string member_error::what() const { return std::visit([this](const auto& payload) -> std::string { using T = std::decay_t; if constexpr (std::is_same_v) { const type resolved = resolve_underlying_type(this->composite); const bool is_enum = resolved.get() != nullptr; const bool is_record = resolved.get() != nullptr; switch (payload) { using enum kind; case not_found: if (is_enum || is_record) { std::string message = is_enum ? "Enumeration" : "Record"; if (auto alias = this->composite.get()) { message += " '" + alias->name + "'"; } message += " does not have a "; message += is_enum ? "member" : "field"; message += " named '" + this->name + "'"; return message; } return "Type '" + this->composite.to_string() + "' does not have a field named '" + this->name + "'"; case field_on_type: return "Cannot access field '" + this->name + "' on type '" + this->composite.to_string() + "'"; default: __builtin_unreachable(); } } else if constexpr (std::is_same_v) { const type resolved = resolve_underlying_type(this->composite); 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 = this->composite.get()) { message += " '" + alias->name + "'"; } message += " already has a " + kind + " named '" + this->name + "'"; if (payload.base.has_value()) { message += " (defined in base type '" + *payload.base + "')"; } return message; } }, payload); } std::optional member_error::note() const { if (std::holds_alternative(this->payload)) { return previous_declaration_note(std::get(this->payload).original); } else { 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; } } /** * Collects field names from a record type recursively, base first. */ static void collect_field_names(const type& composite_type, ordered_map& names) { auto record = erase_generic(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{ .position = std::nullopt, .base_type = composite_type }); } } void forward_declaration_visitor::visit(unit *unit) { for (auto *const unit_declaration: unit->declarations) { unit_declaration->accept(this); } } void forward_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->identifier.id().name(), symbol_declaration_error::redefinition{ .original = declaration->position(), .file = {} }); } } void forward_declaration_visitor::visit(variable_declaration *) { } void forward_declaration_visitor::visit(procedure_declaration *) { } resolving_visitor::resolving_visitor(symbol_bag& bag, const target_info& target, const std::filesystem::path& module_path) : bag(bag), constant_evaluator(this->bag, target), module_file(module_path) { } void resolving_visitor::visit(import_declaration *) { } procedure_type::return_t resolving_visitor::build_return_type( const procedure_type_expression::return_t& return_type) { if (return_type.no_return) { return procedure_type::return_t(std::monostate{}); } else if (return_type.proper_type != nullptr) { return procedure_type::return_t(resolve_type(*return_type.proper_type)); } else { return procedure_type::return_t(); } } std::pair> resolving_visitor::build_procedure( procedure_type_expression& expression, const bool variadic_supported) { const procedure_type::return_t result_return = build_return_type(expression.return_type); std::pair> result_type{ procedure_type(result_return), std::vector() }; for (const auto& [parameter_names, parameters_type] : expression.parameters) { const type parameter_type = resolve_type(*parameters_type); for (const auto& parameter_name : parameter_names) { evaluate_attributes<>(parameter_name.attributes); result_type.first.parameters.push_back(parameter_type); result_type.second.push_back(parameter_name.name()); } } result_type.first.variadic = variadic_supported ? evaluate_attributes(expression.attributes).variadic : evaluate_attributes<>(expression.attributes).variadic; if (result_type.first.variadic && result_type.first.parameters.empty()) { add_error(expression.position(), "varargs", attribute_error::kind::missing_parameter); } return result_type; } std::optional resolving_visitor::parse_attribute(const std::string& name) { if (name == "aligned") { return attribute_kind::aligned; } if (name == "varargs") { return attribute_kind::varargs; } return std::nullopt; } void resolving_visitor::evaluate_alignment(const attribute& written, std::optional& alignment) { if (written.arguments().size() != 1) { add_error(written.name().position(), written.name().name(), attribute_error::kind::argument_count); } else if (alignment.has_value()) { add_error(written.name().position(), written.name().name(), attribute_error::kind::duplicate); } else if (auto value = this->constant_evaluator.evaluate_index(*written.arguments().front())) { if (value.value() == 0 || (value.value() & (value.value() - 1)) != 0) { add_error(written.arguments().front()->position(), written.name().name(), attribute_error::kind::invalid_alignment); } else { alignment = value; } } else { add_error(written.arguments().front()->position(), written.name().name(), attribute_error::kind::not_constant); } } ordered_map resolving_visitor::build_composite_type( const std::vector& fields, ordered_map& field_names, const type& aggregate) { ordered_map result; for (const auto& field : fields) { const type field_type = resolve_type(*field.second); for (const identifier_definition& field_name : field.first) { const std::optional alignment = evaluate_attributes(field_name.attributes).alignment; auto [existing, inserted] = field_names.insert(field_name.name(), field_origin{ .position = field.second->position(), .base_type = type() }); if (!inserted) { std::optional base_name; if (!existing->second.position.has_value() && !existing->second.base_type.empty()) { if (auto alias = existing->second.base_type.get()) { base_name = alias->name; } } add_error(field_name.id().position(), field_name.name(), aggregate, member_error::duplicate{ .original = existing->second.position, .base = base_name }); } else { result.insert(field_name.name(), field_info(field_type, alignment)); } } } return result; } type resolving_visitor::lookup_primitive_type(const std::string& name) { return this->bag.lookup(name)->is_type()->symbol; } std::filesystem::path resolving_visitor::redefinition_file(const std::shared_ptr& original) const { return original->file != this->module_file ? original->file : std::filesystem::path{}; } std::shared_ptr resolving_visitor::register_variable(const std::string& name, const type& variable_type, const source_position position, const bool is_extern) { auto variable_symbol = std::make_shared(variable_type, is_extern); variable_symbol->position.emplace(position); variable_symbol->file = this->module_file; if (!this->bag.enter(name, variable_symbol)) { auto original = this->bag.lookup(name); symbol_declaration_error::redefinition original_definition{ .original = original->position, .file = this->redefinition_file(original) }; add_error(position, name, original_definition); } return variable_symbol; } std::optional resolving_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 resolving_visitor::lookup_field(const type& composite_type, const std::string& field_name) { type record_field = elna::boot::lookup_field(composite_type, field_name); if (!record_field.empty()) { return record_field; } const type resolved_type = resolve_underlying_type(composite_type); 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(); } std::vector resolving_visitor::enter_parameters(const std::vector& parameters) { std::vector result; result.reserve(parameters.size()); for (const identifier& parameter : parameters) { const type parameter_symbol = type(std::make_shared(parameter.name())); auto info = std::make_shared(parameter_symbol); info->position.emplace(parameter.position()); info->file = this->module_file; if (this->bag.enter(parameter.name(), info)) { result.push_back(parameter_symbol); } else { auto original = this->bag.lookup(parameter.name()); add_error(parameter.position(), parameter.name(), symbol_declaration_error::redefinition{ .original = original->position, .file = this->redefinition_file(original) }); result.push_back(parameter_symbol); } } return result; } type resolving_visitor::resolve_type(type_expression& expression) { expression.accept(this); // An unknown name has reported itself already; only a name that resolves // to something other than a type is left to report. if (this->current_type.empty()) { if (auto *named = expression.is_named(); named != nullptr && this->bag.lookup(named->name) != nullptr) { add_error(named->position(), named->name, symbol_declaration_error::kind::not_a_type); } } return this->current_type; } void resolving_visitor::visit(array_type_expression *expression) { const type array_base = resolve_type(expression->base()); if (array_base.empty()) { this->current_type = type(); return; } if (array_base.get() != nullptr) { add_error(expression->position(), declaration_format_error::kind::array_position); } expression->dimensions().accept(this); if (expression->dimensions().type_decoration.empty()) { // The dimension expression failed to resolve and reported its own // error already. this->current_type = type(); return; } const auto size_constant = this->constant_evaluator.evaluate_index(expression->dimensions()); if (!size_constant.has_value()) { add_error(expression->position(), non_constant_expression_error::array_dimensions{ array_base }); this->current_type = type(); return; } this->current_type = type(std::make_shared(array_base, size_constant.value())); } void resolving_visitor::visit(slice_type_expression *expression) { const type slice_base = resolve_type(expression->base()); this->current_type = slice_base.empty() ? type() : type(std::make_shared(slice_base)); } void resolving_visitor::visit(pointer_type_expression *expression) { const type pointer_base = resolve_type(expression->base()); this->current_type = pointer_base.empty() ? type() : type(std::make_shared(pointer_base)); } void resolving_visitor::visit(constant_type_expression *expression) { const type qualified_base = resolve_type(expression->base()); if (qualified_base.get() != nullptr) { add_error(expression->position(), declaration_format_error::kind::duplicate); } this->current_type = qualified_base.empty() ? type() : type(std::make_shared(qualified_base)); } void resolving_visitor::visit(record_type_expression *expression) { std::shared_ptr result_type; if (expression->base.has_value()) { type base_type; if (auto unresolved_alias = this->bag.declared(expression->base.value().name())) { base_type = type(unresolved_alias); } else if (auto base_symbol = this->bag.lookup(expression->base.value().name())) { if (auto base_type_info = base_symbol->is_type()) { base_type = base_type_info->symbol; } else { add_error(expression->base.value().position(), expression->base.value().name(), symbol_declaration_error::kind::not_a_type); this->current_type = type(); return; } } else { add_error(expression->base.value().position(), expression->base.value().name(), symbol_declaration_error::kind::undeclared_type); this->current_type = type(); return; } if (!expression->base_arguments.empty()) { base_type = type(std::make_shared(base_type, resolve_arguments(expression->base_arguments))); } result_type = std::make_shared(base_type); } else { result_type = std::make_shared(); } ordered_map field_names; collect_field_names(result_type->base, field_names); result_type->fields = build_composite_type(expression->fields, field_names, type(result_type)); result_type->alignment = evaluate_attributes(expression->attributes).alignment; this->current_type = type(result_type); } void resolving_visitor::visit(procedure_type_expression *expression) { std::shared_ptr const result_type = std::make_shared(std::move(build_procedure(*expression, true).first)); this->current_type = type(result_type); } void resolving_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.name(), aggregate, member_error::duplicate{ .original = existing->second, .base = std::nullopt }); } else { seen.insert({ member.name(), member.position() }); } } this->current_type = type(result_type); } void resolving_visitor::visit(extern_type_expression *) { this->current_type = type(std::make_shared()); } void resolving_visitor::visit(assign_statement *statement) { statement->lvalue().accept(this); statement->rvalue().accept(this); } void resolving_visitor::traverse_block(block& body) { for (variable_declaration *const variable : body.variables) { variable->accept(this); } for (statement *const body_statement : body.statements) { body_statement->accept(this); } } void resolving_visitor::visit_block(block& body) { body.symbols = this->bag.enter(); traverse_block(body); this->bag.leave(); } void resolving_visitor::visit(if_statement *statement) { statement->branch().prerequisite().accept(this); visit_block(statement->branch().body); for (conditional_statements *branch : statement->branches) { branch->prerequisite().accept(this); visit_block(branch->body); } if (statement->alternative != nullptr) { visit_block(*statement->alternative); } } void resolving_visitor::visit(while_statement *statement) { statement->branch().prerequisite().accept(this); visit_block(statement->branch().body); for (conditional_statements *branch : statement->branches) { branch->prerequisite().accept(this); visit_block(branch->body); } } void resolving_visitor::visit(repeat_statement *statement) { statement->condition().accept(this); visit_block(statement->body); } void resolving_visitor::visit(defer_statement *statement) { ++this->defer_depth; visit_block(statement->body); --this->defer_depth; } void resolving_visitor::visit(block_statement *statement) { const std::string& label_name = statement->name.name(); statement->body.symbols = this->bag.enter(); auto label_symbol = std::make_shared(this->defer_depth); label_symbol->position.emplace(statement->name.position()); label_symbol->file = this->module_file; if (!this->bag.enter(label_name, label_symbol)) { auto original = this->bag.lookup(label_name); symbol_declaration_error::redefinition original_definition{ .original = original->position, .file = this->redefinition_file(original) }; add_error(statement->name.position(), label_name, original_definition); } traverse_block(statement->body); this->bag.leave(); } void resolving_visitor::visit(break_statement *statement) { const std::string& label_name = statement->label.name(); const source_position position = statement->label.position(); const std::shared_ptr symbol = this->bag.lookup(label_name); if (symbol == nullptr) { add_error(position, label_name, symbol_declaration_error::kind::undeclared_label); return; } auto label_symbol = symbol->is_label(); if (label_symbol == nullptr) { add_error(position, label_name, symbol_declaration_error::kind::not_a_label); } else if (label_symbol->defer_depth != this->defer_depth) { add_error(position, label_name, symbol_declaration_error::kind::break_leaves_defer); } } void resolving_visitor::visit(empty_statement *) { } void resolving_visitor::visit(case_statement *statement) { statement->condition().accept(this); for (switch_case& case_block : statement->cases) { for (expression *case_label : case_block.labels) { case_label->accept(this); } visit_block(case_block.body); } if (statement->alternative != nullptr) { visit_block(*statement->alternative); } } void resolving_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); } this->current_type = type(); } void resolving_visitor::visit(cast_expression *expression) { expression->value().accept(this); expression->target().accept(this); expression->type_decoration = this->current_type; this->current_type = type(); } void resolving_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 = expression->arguments.empty() ? type_info->symbol : type(std::make_shared(type_info->symbol, resolve_arguments(expression->arguments))); } } else { add_error(expression->type_name.position(), expression->type_name.name(), symbol_declaration_error::kind::undeclared_type); } 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(), initializer.id().name(), expression->type_decoration, member_error::kind::not_found); } } this->current_type = type(); } void resolving_visitor::visit(array_constructor_expression *expression) { for (auto *element : expression->elements) { element->accept(this); } const type element_type = expression->elements.front()->type_decoration; expression->type_decoration = type(std::make_shared(element_type, expression->elements.size())); this->current_type = type(); } void resolving_visitor::visit(slicing_expression *expression) { expression->base().accept(this); 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); } expression->start().accept(this); expression->end().accept(this); this->current_type = type(); } void resolving_visitor::visit(traits_expression *trait) { if (!trait->arguments.empty()) { trait->types.push_back(resolve_type(*trait->arguments.front())); } 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(); } else { add_error(trait->name.position(), trait->name.name(), symbol_declaration_error::kind::undeclared_trait); } this->current_type = type(); } void resolving_visitor::visit(binary_expression *expression) { expression->lhs().accept(this); expression->rhs().accept(this); 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; } this->current_type = type(); } void resolving_visitor::visit(unary_expression *expression) { expression->operand().accept(this); if (expression->operation() == unary_operator::reference) { expression->type_decoration = type( std::make_shared(expression->operand().type_decoration)); } else { expression->type_decoration = expression->operand().type_decoration; } this->current_type = type(); } void resolving_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()) { type procedure = type(std::make_shared(procedure_symbol->symbol)); if (!expression->arguments.empty()) { expression->argument_types = resolve_arguments(expression->arguments); procedure = substitute(procedure, procedure_symbol->parameters, expression->argument_types); } expression->type_decoration = procedure; return; } } else { add_error(expression->position(), expression->name, symbol_declaration_error::kind::undeclared_symbol); } if (!expression->arguments.empty() && !this->current_type.empty()) { const type generic = this->current_type; expression->argument_types = resolve_arguments(expression->arguments); this->current_type = type(std::make_shared(generic, std::vector(expression->argument_types))); } // Type check decides whether the name was applied correctly, so what a // type expression resolved to has to reach it. if (expression->type_decoration.empty()) { expression->type_decoration = this->current_type; } } std::vector resolving_visitor::resolve_arguments(const std::vector& arguments) { std::vector result; result.reserve(arguments.size()); for (type_expression *argument : arguments) { result.push_back(resolve_type(*argument)); } return result; } void resolving_visitor::visit(array_access_expression *expression) { expression->base().accept(this); expression->index().accept(this); 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); } this->current_type = type(); } void resolving_visitor::visit(field_access_expression *expression) { expression->base().accept(this); // Handling field access on a type (only valid for enumerations). if (!this->current_type.empty()) { auto resolved_base = resolve_underlying_type(this->current_type); if (auto enumeration_base = resolved_base.get()) { auto member_iterator = std::ranges::find(enumeration_base->members, expression->field().name()); if (member_iterator == enumeration_base->members.cend()) { add_error(expression->field().position(), expression->field().name(), this->current_type, member_error::kind::not_found); } else { expression->type_decoration = this->current_type; } } else { add_error(expression->field().position(), expression->field().name(), this->current_type, member_error::kind::field_on_type); } } else // Handling field access on a value. { expression->type_decoration = lookup_field(expression->base().type_decoration, expression->field().name()); if (expression->type_decoration.empty()) { add_error(expression->field().position(), expression->field().name(), expression->base().type_decoration, member_error::kind::not_found); } else { expression->type_decoration = qualify_member_type(expression->type_decoration, expression->base().type_decoration); } } this->current_type = type(); } void resolving_visitor::visit(dereference_expression *expression) { expression->base().accept(this); if (auto pointer = resolve_underlying_type(expression->base().type_decoration).get()) { expression->type_decoration = pointer->base; } this->current_type = type(); } void resolving_visitor::visit(literal *literal) { if (literal->value.is_signed()) { if (literal->has_explicit_size) { literal->type_decoration = lookup_primitive_type( "Int" + std::to_string(literal->value.size() * CHAR_BIT)); } else { literal->type_decoration = lookup_primitive_type("Int"); } } else { if (literal->has_explicit_size) { literal->type_decoration = lookup_primitive_type( "Word" + std::to_string(literal->value.size() * CHAR_BIT)); } else { literal->type_decoration = lookup_primitive_type("Word"); } } this->current_type = type(); } void resolving_visitor::visit(literal *literal) { literal->type_decoration = literal->value.format() == float_literal::format_kind::binary32 ? lookup_primitive_type("Single") : lookup_primitive_type("Double"); this->current_type = type(); } void resolving_visitor::visit(literal *literal) { literal->type_decoration = lookup_primitive_type("Bool"); this->current_type = type(); } void resolving_visitor::visit(literal *literal) { literal->type_decoration = lookup_primitive_type("Char"); this->current_type = type(); } void resolving_visitor::visit(literal *literal) { literal->type_decoration = lookup_primitive_type("Pointer"); this->current_type = type(); } void resolving_visitor::visit(literal *literal) { literal->type_decoration = type(std::make_shared( type(std::make_shared(lookup_primitive_type("Word8"))))); this->current_type = type(); } declaration_visitor::declaration_visitor(symbol_bag& bag, const target_info& target, const std::filesystem::path& module_path) : resolving_visitor(bag, target, module_path) { } std::optional> declaration_visitor::find_alias_cycle( const std::shared_ptr& being_resolved, const type& referent) { std::vector alias_path; return find_alias_cycle(being_resolved, referent, alias_path, walk_state{}) ? std::nullopt : std::make_optional(alias_path); } bool declaration_visitor::find_alias_cycle(const std::shared_ptr& being_resolved, const type& referent, std::vector& alias_path, walk_state state) { /* * A recursive type is legal only if the cycle passes through a record, * which materializes the recursion, and the occurrence closing the * cycle is behind a pointer or a slice, so that the record layout * stays finite. All other cycles are rejected. */ if (auto link = referent.get()) { if (std::ranges::find(alias_path, link->name) != std::cend(alias_path)) { // A legal cycle already checked on this path. return true; } alias_path.push_back(link->name); if (being_resolved == link) { return state.record_seen && state.guarded; } const bool acyclic = find_alias_cycle(being_resolved, link->referent, alias_path, state); if (acyclic) { alias_path.pop_back(); } return acyclic; } else if (auto link = referent.get()) { return find_alias_cycle(being_resolved, link->unqualified, alias_path, state); } else if (auto link = referent.get()) { walk_state guarded_state = state; guarded_state.guarded = true; return find_alias_cycle(being_resolved, link->base, alias_path, guarded_state); } else if (auto link = referent.get()) { walk_state guarded_state = state; guarded_state.guarded = true; return find_alias_cycle(being_resolved, link->base, alias_path, guarded_state); } else if (auto link = referent.get()) { return find_alias_cycle(being_resolved, link->base, alias_path, state); } else if (auto link = referent.get()) { return find_alias_cycle(being_resolved, link->referent, alias_path, state); } else if (auto link = referent.get()) { // Erasure is argument blind, so the arguments cannot close a cycle // the generic does not close on its own. This is what lets // polymorphic recursion terminate. return find_alias_cycle(being_resolved, link->generic, alias_path, state); } else if (auto link = referent.get()) { const std::size_t saved_path = alias_path.size(); for (const type& parameter : link->parameters) { alias_path.resize(saved_path); if (!find_alias_cycle(being_resolved, parameter, alias_path, state)) { return false; } } if (!link->return_type.proper_type.empty()) { alias_path.resize(saved_path); return find_alias_cycle(being_resolved, link->return_type.proper_type, alias_path, state); } return true; } else if (auto link = referent.get()) { const std::size_t saved_path = alias_path.size(); walk_state in_record = state; in_record.record_seen = true; in_record.guarded = false; if (!link->base.empty() && !find_alias_cycle(being_resolved, link->base, alias_path, in_record)) { return false; } for (const auto& [field_name, field] : link->fields) { alias_path.resize(saved_path); if (!find_alias_cycle(being_resolved, field.field_type, alias_path, in_record)) { return false; } } return true; } return true; } void declaration_visitor::visit(unit *unit) { for (declaration *const unit_declaration : unit->declarations) { unit_declaration->accept(this); } if (unit->entry_point.has_value()) { auto word8_primitive = lookup_primitive_type("Word8"); const procedure_type::return_t result_return = procedure_type::return_t(word8_primitive); auto heading = procedure_type(result_return); std::shared_ptr info; if (unit->parameters.size() > 1) { add_error(unit->position(), declaration_format_error::kind::module_entry); return; } else if (unit->parameters.size() == 1) { auto variable_type = type(std::make_shared(word8_primitive)); variable_type = type(std::make_shared(variable_type)); heading.parameters.push_back(variable_type); info = std::make_shared(heading, std::vector({ unit->parameters.at(0).name() }), this->bag.enter()); register_variable(info->names.at(0), variable_type, unit->parameters.at(0).position()); } else { info = std::make_shared(heading, std::vector{}, this->bag.enter()); } this->bag.leave(); info->position = unit->entry_position; info->file = this->module_file; // A unit has at most one entry point and an imported program is // rejected before its symbols are merged, so this cannot collide. this->bag.enter("", info); } } void declaration_visitor::visit(type_declaration *declaration) { const bool is_generic = !declaration->parameters.empty(); std::vector parameters; if (is_generic) { this->bag.enter(); parameters = enter_parameters(declaration->parameters); } type underlying = resolve_type(declaration->underlying_type()); if (is_generic) { this->bag.leave(); underlying = type(std::make_shared(std::move(parameters), underlying)); } // Reject the cycle and wire an empty referent: the declaration still // resolves and is entered, so its uses degrade silently. if (auto cycle = find_alias_cycle( this->bag.declared(declaration->identifier.name()), underlying)) { add_error(declaration->position(), *cycle); underlying = type(); } const std::shared_ptr resolved = this->bag.resolve(declaration->identifier.name(), underlying); auto info = std::make_shared(type(resolved)); info->exported = declaration->identifier.exported(); info->position.emplace(declaration->position()); info->file = this->module_file; if (!this->bag.enter(declaration->identifier.name(), info)) { auto original = this->bag.lookup(declaration->identifier.name()); symbol_declaration_error::redefinition original_definition{ .original = original->position, .file = this->redefinition_file(original) }; add_error(declaration->identifier.id().position(), declaration->identifier.name(), original_definition); } } void declaration_visitor::visit(variable_declaration *declaration) { const type variable_type = resolve_type(declaration->variable_type()); std::optional computed; if (declaration->initializer != nullptr) { declaration->initializer->accept(this); computed = this->constant_evaluator.evaluate(*declaration->initializer); if (!computed.has_value()) { add_error(declaration->initializer->position(), non_constant_expression_error::initializer{ extract_identifiers(declaration->identifiers) }); } } else if (!declaration->is_extern && resolve_aliases(variable_type).get() != nullptr) { auto position_span = source_position( declaration->identifiers.front().id().position().start(), declaration->identifiers.back().id().position().end()); add_error(position_span, declaration_format_error::not_initialized{ extract_identifiers(declaration->identifiers) }); } for (const auto& variable_identifier : declaration->identifiers) { auto variable_symbol = register_variable(variable_identifier.name(), variable_type, declaration->position(), declaration->is_extern); variable_symbol->exported = variable_identifier.exported(); variable_symbol->alignment = evaluate_attributes(variable_identifier.attributes).alignment; variable_symbol->value = computed; variable_symbol->is_thread_local = declaration->is_thread_local; } } void declaration_visitor::visit(procedure_declaration *declaration) { // The scope is opened before the heading is built so that the type // parameters are visible in it, and it becomes the body's scope, which // is why the body sees them too. const std::shared_ptr scope = this->bag.enter(); const std::vector type_parameters = enter_parameters(declaration->parameters); auto [heading, parameter_names] = build_procedure(declaration->heading(), !declaration->body.has_value()); std::shared_ptr info; if (declaration->body.has_value()) { info = std::make_shared(heading, std::move(parameter_names), scope); auto name_iterator = std::cbegin(info->names); auto type_iterator = std::cbegin(info->symbol.parameters); while (name_iterator != std::cend(info->names) && type_iterator != std::cend(info->symbol.parameters)) { auto variable_symbol = register_variable(*name_iterator, *type_iterator, declaration->heading().position()); ++name_iterator; ++type_iterator; } } else { info = std::make_shared(heading, std::move(parameter_names)); } this->bag.leave(); info->parameters = type_parameters; info->exported = declaration->identifier.exported(); info->position.emplace(declaration->position()); info->file = this->module_file; if (!this->bag.enter(declaration->identifier.name(), info)) { auto original = this->bag.lookup(declaration->identifier.name()); symbol_declaration_error::redefinition original_definition{ .original = original->position, .file = this->redefinition_file(original) }; add_error(declaration->identifier.id().position(), declaration->identifier.name(), original_definition); } } void declaration_visitor::visit(for_statement *) { __builtin_unreachable(); } name_analysis_visitor::name_analysis_visitor(symbol_bag& bag, const target_info& target, const std::filesystem::path& module_path) : resolving_visitor(bag, target, module_path) { } void name_analysis_visitor::visit(unit *unit) { for (declaration *const unit_declaration : unit->declarations) { unit_declaration->accept(this); } if (unit->entry_point.has_value()) { const std::shared_ptr info = this->bag.lookup("")->is_procedure(); unit->entry_point->symbols = info->scope; this->bag.enter(info->scope); traverse_body(this, unit->entry_point.value()); this->bag.leave(); } } void name_analysis_visitor::visit(type_declaration *) { } void name_analysis_visitor::visit(variable_declaration *declaration) { if (!this->bag.is_global()) { const type variable_type = resolve_type(declaration->variable_type()); if (declaration->initializer != nullptr) { declaration->initializer->accept(this); } else if (resolve_aliases(variable_type).get() != nullptr) { auto position_span = source_position( declaration->identifiers.front().id().position().start(), declaration->identifiers.back().id().position().end()); add_error(position_span, declaration_format_error::not_initialized{ extract_identifiers(declaration->identifiers) }); } for (const auto& variable_identifier : declaration->identifiers) { auto variable_symbol = register_variable(variable_identifier.name(), variable_type, declaration->position(), declaration->is_extern); variable_symbol->exported = variable_identifier.exported(); variable_symbol->alignment = evaluate_attributes(variable_identifier.attributes).alignment; } } } void name_analysis_visitor::visit(procedure_declaration *declaration) { if (declaration->body.has_value()) { const std::shared_ptr info = this->bag.lookup(declaration->identifier.name())->is_procedure(); declaration->body->symbols = info->scope; this->bag.enter(info->scope); traverse_body(this, declaration->body.value()); this->bag.leave(); } } 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)); const type control_variable_const_type = type(std::make_shared(control_variable_pointer_type)); statement->body.symbols = this->bag.enter(); register_variable(statement->control_variable.name(), control_variable_const_type, statement->control_variable.position()); if (statement->counter != nullptr) { register_variable(statement->counter->name(), lookup_primitive_type("Word"), statement->counter->position()); } traverse_block(statement->body); this->bag.leave(); } }