From 36a274c9a8bca944234589220def025d3920b3ef Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Tue, 28 Jul 2026 19:19:32 +0200 Subject: Enforce case label uniqueness and constness --- boot/dependency.cc | 13 ++- boot/evaluator.cc | 224 +++++--------------------------------------------- boot/name_analysis.cc | 1 + boot/result.cc | 75 +++++++++++++++++ boot/symbol.cc | 40 --------- boot/type_check.cc | 12 +-- boot/validation.cc | 155 ++++++++++++++++++++++++++++++++++ 7 files changed, 261 insertions(+), 259 deletions(-) create mode 100644 boot/validation.cc (limited to 'boot') diff --git a/boot/dependency.cc b/boot/dependency.cc index 63d3952..a44b1f5 100644 --- a/boot/dependency.cc +++ b/boot/dependency.cc @@ -17,13 +17,10 @@ along with GCC; see the file COPYING3. If not see #include "elna/boot/dependency.h" -#include -#include -#include - #include "elna/boot/driver.h" #include "elna/boot/name_analysis.h" #include "elna/boot/type_check.h" +#include "elna/boot/validation.h" #include "parser.hh" namespace elna::boot @@ -73,12 +70,12 @@ namespace elna::boot { return std::move(type_analyzer.errors()); } - constant_folder folder(bag, target); - tree->accept(&folder); + validation_visitor validator(bag, target); + tree->accept(&validator); - if (folder.has_errors()) + if (validator.has_errors()) { - return std::move(folder.errors()); + return std::move(validator.errors()); } return error_list{}; } diff --git a/boot/evaluator.cc b/boot/evaluator.cc index 6d3153e..b49a9ec 100644 --- a/boot/evaluator.cc +++ b/boot/evaluator.cc @@ -16,25 +16,17 @@ along with GCC; see the file COPYING3. If not see . */ #include "elna/boot/evaluator.h" + #include "elna/boot/ast.h" #include #include +#include #include #include namespace elna::boot { - non_constant_initializer_error::non_constant_initializer_error(const source_position position) - : error(position) - { - } - - std::string non_constant_initializer_error::what() const - { - return "Variable initializers must be constant expressions"; - } - std::optional get_type_properties(const type& subject, const target_info& target) { auto resolved = resolve_underlying_type(subject); @@ -304,6 +296,23 @@ namespace elna::boot std::optional evaluator::evaluate_field_access(field_access_expression& subject) { + auto type_to_check = subject.base().type_decoration; + if (type_to_check.empty()) + { + type_to_check = subject.type_decoration; + } + auto resolved_base = resolve_underlying_type(type_to_check); + if (auto enumeration = resolved_base.get()) + { + auto member_iterator = std::ranges::find(enumeration->members, subject.field().name()); + if (member_iterator != enumeration->members.end()) + { + return constant_value{ + static_cast(std::distance(enumeration->members.begin(), member_iterator) + 1) + }; + } + return std::nullopt; + } auto base = evaluate(subject.base()); if (!base.has_value()) { @@ -770,199 +779,4 @@ namespace elna::boot return std::nullopt; } - /** - * Converts a constant_value to an AST literal expression. - * - * \param value Evaluated constant to convert. - * \param position Source position for the new literal node. - * \param decoration Type decoration to apply to the literal. - * \return A new literal expression, or \c nullptr if \p value - * is an aggregate (handled separately by the caller). - */ - static expression *value_to_expression(const constant_value& value, - const source_position& position, const type& decoration) - { - return std::visit([&position, &decoration](auto&& value) -> expression* - { - using T = std::decay_t; - - if constexpr (std::is_same_v) - { - auto *lit = new literal(position, value); - lit->type_decoration = decoration; - return lit; - } - else if constexpr (std::is_same_v) - { - auto *lit = new literal(position, value); - lit->type_decoration = decoration; - return lit; - } - else if constexpr (std::is_same_v) - { - auto *lit = new literal(position, value); - lit->type_decoration = decoration; - return lit; - } - else if constexpr (std::is_same_v) - { - auto *lit = new literal(position, value); - lit->type_decoration = decoration; - return lit; - } - else if constexpr (std::is_same_v) - { - auto *lit = new literal(position, value); - lit->type_decoration = decoration; - return lit; - } - else if constexpr (std::is_same_v - || std::is_same_v) - { - auto *lit = new literal(position, nullptr); - lit->type_decoration = decoration; - return lit; - } - else - { - return nullptr; - } - }, value); - } - - expression *evaluator::fold(expression& original) - { - auto value = evaluate(original); - if (!value.has_value()) - { - return &original; - } - if (expression *literal = value_to_expression(value.value(), original.position(), original.type_decoration)) - { - delete &original; - return literal; - } - if (auto *record = original.is_record_constructor()) - { - for (auto& field_init : record->field_initializers) - { - fold_aggregate_field(field_init); - } - } - else if (auto *array = original.is_array_constructor()) - { - for (auto& element : array->elements) - { - element = fold(*element); - } - } - return &original; - } - - void evaluator::fold_aggregate_field(field_initializer& field_init) - { - auto value = evaluate(field_init.value()); - if (!value.has_value()) - { - return; - } - expression *literal = value_to_expression(value.value(), - field_init.value().position(), field_init.value().type_decoration); - if (literal != nullptr) - { - field_init.value(*literal); - return; - } - expression *folded = fold(field_init.value()); - if (folded != &field_init.value()) - { - field_init.value(*folded); - } - } - - constant_folder::constant_folder(symbol_bag& bag, const target_info& target) - : bag(bag), target(target), constant_evaluator(this->bag, this->target) - { - } - - void constant_folder::visit(variable_declaration* declaration) - { - if (declaration->initializer == nullptr || has_errors()) - { - return; - } - auto computed = this->constant_evaluator.evaluate(*declaration->initializer); - if (!computed) - { - add_error(declaration->initializer->position()); - return; - } - declaration->initializer = this->constant_evaluator.fold(*declaration->initializer); - - for (const auto& identifier : declaration->identifiers) - { - auto symbol = this->bag.lookup(identifier.name()); - if (symbol == nullptr) - { - continue; - } - if (auto var = symbol->is_variable(); - resolve_aliases(var->symbol).get() != nullptr) - { - var->value = computed; - } - } - } - - void constant_folder::visit(cast_expression *expr) - { - expr->target().accept(this); - expr->value().accept(this); - expr->value(fold_trait(expr->value())); - } - - void constant_folder::visit(binary_expression *expr) - { - expr->lhs().accept(this); - expr->lhs(fold_trait(expr->lhs())); - expr->rhs().accept(this); - expr->rhs(fold_trait(expr->rhs())); - } - - void constant_folder::visit(unary_expression *expr) - { - expr->operand().accept(this); - expr->operand(fold_trait(expr->operand())); - } - - void constant_folder::visit(procedure_call *call) - { - call->callable().accept(this); - for (auto& argument : call->arguments) - { - argument->accept(this); - expression& folded = fold_trait(*argument); - if (&folded != argument) - { - delete argument; - argument = &folded; - } - } - } - - expression& constant_folder::fold_trait(expression& expr) - { - if (auto *trait = expr.is_traits()) - { - if (auto value = this->constant_evaluator.evaluate_traits(*trait)) - { - if (auto *literal = value_to_expression(value.value(), trait->position(), trait->type_decoration)) - { - return *literal; - } - } - } - return expr; - } - } diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc index 07f6d31..077b5d6 100644 --- a/boot/name_analysis.cc +++ b/boot/name_analysis.cc @@ -17,6 +17,7 @@ along with GCC; see the file COPYING3. If not see #include "elna/boot/name_analysis.h" +#include #include namespace elna::boot diff --git a/boot/result.cc b/boot/result.cc index 86b0e7a..0eb5c12 100644 --- a/boot/result.cc +++ b/boot/result.cc @@ -17,6 +17,8 @@ along with GCC; see the file COPYING3. If not see #include "elna/boot/result.h" +#include + namespace elna::boot { location::location(const std::size_t line, const std::size_t column) @@ -137,6 +139,49 @@ namespace elna::boot { return this->m_exported; } + + std::size_t constant_value_hash::operator()(const elna::boot::constant_value& value) const noexcept + { + return std::visit([](auto&& alternative) -> std::size_t { + using T = std::decay_t; + + return std::hash{}(alternative); + }, value); + } + + bool constant_value_hash::operator()(const elna::boot::constant_value& lhs, + const elna::boot::constant_value& rhs) const noexcept + { + return std::visit([](auto&& first, auto&& second) -> bool { + using T = std::decay_t; + using U = std::decay_t; + + if constexpr (std::is_same_v) + { + return first == second; + } + else + { + return false; + } + }, lhs, rhs); + } + + hash_accumulator hash_accumulator::operator+(const std::size_t& that) const + { + hash_accumulator result{}; + + result.m_seed ^= that + hash_accumulator::golden_ratio + // NOLINTNEXTLINE(readability-magic-numbers) + + (this->m_seed << 6) + (this->m_seed >> 2); + + return result; + } + + std::size_t hash_accumulator::seed() const + { + return this->m_seed; + } } std::size_t std::hash::operator()( @@ -144,3 +189,33 @@ std::size_t std::hash::operator()( { return std::hash{}(key.name()); } + +std::size_t std::hash::operator()( + const elna::boot::global_address& key) const noexcept +{ + return std::hash{}(key.name); +} + +std::size_t std::hash>::operator()( + const elna::boot::constant_aggregate& key) const noexcept +{ + const elna::boot::constant_value_hash hasher{}; + auto hash = std::accumulate(key->begin(), key->end(), elna::boot::hash_accumulator{}, + [&hasher](const auto& accumulator, const auto& element) { + return accumulator + hasher(element); + }); + + return hash.seed(); +} + +std::size_t std::hash>::operator()( + const elna::boot::constant_aggregate& key) const noexcept +{ + const elna::boot::constant_value_hash hasher{}; + auto hash = std::accumulate(key->begin(), key->end(), elna::boot::hash_accumulator{}, + [&hasher](const auto& accumulator, const auto& element) { + return accumulator + std::hash{}(element.first) + hasher(element.second); + }); + + return hash.seed(); +} diff --git a/boot/symbol.cc b/boot/symbol.cc index 5e303fb..e0a89e6 100644 --- a/boot/symbol.cc +++ b/boot/symbol.cc @@ -26,46 +26,6 @@ namespace elna::boot { } - type::type(std::shared_ptr primitive) - : payload(primitive) - { - } - - type::type(std::shared_ptr record) - : payload(record) - { - } - - type::type(std::shared_ptr pointer) - : payload(pointer) - { - } - - type::type(std::shared_ptr constant) - : payload(constant) - { - } - - type::type(std::shared_ptr array) - : payload(array) - { - } - - type::type(std::shared_ptr slice) - : payload(slice) - { - } - - type::type(std::shared_ptr procedure) - : payload(procedure) - { - } - - type::type(std::shared_ptr enumeration) - : payload(enumeration) - { - } - template std::shared_ptr type::get() const { diff --git a/boot/type_check.cc b/boot/type_check.cc index 2bb0ddd..343b5aa 100644 --- a/boot/type_check.cc +++ b/boot/type_check.cc @@ -198,9 +198,9 @@ namespace elna::boot } else if (auto record = referent.get()) { - for (const type_field& field : record->fields) + for (const auto& [field_name, field_type] : record->fields) { - if (contains_constant_member(field.second)) + if (contains_constant_member(field_type)) { return true; } @@ -660,14 +660,14 @@ namespace elna::boot } for (const field_initializer& initializer : expression->field_initializers) { - for (const type_field& field : record->fields) + for (const auto& [field_name, field_type]: record->fields) { - if (field.first == initializer.name()) + if (field_name == initializer.name()) { - if (!is_assignable_from(field.second, initializer.value().type_decoration)) + if (!is_assignable_from(field_type, initializer.value().type_decoration)) { add_error( - initializer.value().position(), field.second, + initializer.value().position(), field_type, initializer.value().type_decoration); } break; diff --git a/boot/validation.cc b/boot/validation.cc new file mode 100644 index 0000000..ccc8865 --- /dev/null +++ b/boot/validation.cc @@ -0,0 +1,155 @@ +/* Final validation after constant folding. + 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/validation.h" + +#include +#include +#include + +namespace elna::boot +{ + validation_error::validation_error(const source_position position, payload_type payload) + : error(position), payload(std::move(payload)) + { + } + + std::string validation_error::what() const + { + return std::visit([](const auto& payload) -> std::string { + using T = std::decay_t; + + if constexpr (std::is_same_v) + { + return "Variable initializers must be constant expressions"; + } + else if constexpr (std::is_same_v) + { + return "Duplicate case label"; + } + else if constexpr (std::is_same_v) + { + return "Case label must be a constant expression"; + } + }, this->payload); + } + + std::optional> validation_error::note() const + { + return std::visit([](const auto& payload) -> std::optional> { + using T = std::decay_t; + + if constexpr (std::is_same_v) + { + std::string identifier_list = std::accumulate( + std::next(payload.identifiers.begin()), payload.identifiers.end(), + payload.identifiers.front().name(), + [](const std::string& accumulator, const identifier& next) -> std::string { + return accumulator + ", " + next.name(); + }); + auto position_span = source_position(payload.identifiers.front().position().start(), + payload.identifiers.back().position().end()); + return std::make_pair(std::move(identifier_list), position_span); + } + else if constexpr (std::is_same_v) + { + return std::make_pair("Previous label here", payload.first); + } + else if constexpr (std::is_same_v) + { + return std::nullopt; + } + }, this->payload); + } + + validation_error validation_error::non_constant_initializer_error(const source_position position, + const std::vector& identifiers) + { + non_constant_initializer payload; + payload.identifiers.reserve(identifiers.size()); + + std::ranges::transform(identifiers, std::back_inserter(payload.identifiers), + [](const auto& identifier) { return identifier.id(); }); + + return validation_error(position, std::move(payload)); + } + + validation_visitor::validation_visitor(symbol_bag& bag, const target_info& target) + : bag(bag), target(target), constant_evaluator(this->bag, this->target) + { + } + + void validation_visitor::visit(variable_declaration* declaration) + { + if (declaration->initializer == nullptr || has_errors()) + { + return; + } + auto computed = this->constant_evaluator.evaluate(*declaration->initializer); + if (!computed) + { + auto non_constant_initializer_error = validation_error::non_constant_initializer_error( + declaration->initializer->position(), declaration->identifiers); + add_error(non_constant_initializer_error); + return; + } + for (const auto& identifier : declaration->identifiers) + { + auto variable_symbol = this->bag.lookup(identifier.name())->is_variable(); + variable_symbol->value = computed; + } + } + + void validation_visitor::visit(procedure_declaration *declaration) + { + if (declaration->body.has_value()) + { + auto procedure = this->bag.lookup(declaration->identifier.name())->is_procedure(); + this->bag.enter(procedure->scope); + } + walking_visitor::visit(declaration); + if (declaration->body.has_value()) + { + this->bag.leave(); + } + } + + void validation_visitor::visit(case_statement *statement) + { + walking_visitor::visit(statement); + std::unordered_map seen; + for (const auto& case_block : statement->cases) + { + for (auto *label : case_block.labels) + { + auto value = this->constant_evaluator.evaluate(*label); + if (!value.has_value()) + { + add_error(label->position(), + validation_error::non_constant_case_label{}); + continue; + } + auto [case_position, inserted] = seen.try_emplace(value.value(), label->position()); + if (!inserted) + { + add_error(label->position(), + validation_error::duplicate_case{ case_position->second }); + } + } + } + } +} -- cgit v1.2.3