/* 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,
ordered_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 });
}
}
ordered_map name_analysis_visitor::build_composite_type(
const std::vector& fields,
ordered_map& field_names,
const type& aggregate)
{
ordered_map result;
for (const auto& field : fields)
{
field.second->accept(this);
for (const auto& field_name : field.first)
{
auto [existing, inserted] = field_names.insert(field_name.name(),
field_origin{ .declaration = field.second->position(), .base_type = type() });
if (!inserted)
{
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
{
result.insert(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();
}
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));
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()});
}
}
}
}