diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-07-29 01:16:18 +0200 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-07-29 01:16:18 +0200 |
| commit | f8daedce5c73e02dfb2fc59d75777190185584df (patch) | |
| tree | 9ec211b96c3d7efd2fca0da37baeaa8b341e31fb /boot/type_check.cc | |
| parent | 36a274c9a8bca944234589220def025d3920b3ef (diff) | |
| download | elna-f8daedce5c73e02dfb2fc59d75777190185584df.tar.gz | |
Validate cast compatibility during semantic analysis
Diffstat (limited to 'boot/type_check.cc')
| -rw-r--r-- | boot/type_check.cc | 406 |
1 files changed, 261 insertions, 145 deletions
diff --git a/boot/type_check.cc b/boot/type_check.cc index 343b5aa..07deb2d 100644 --- a/boot/type_check.cc +++ b/boot/type_check.cc @@ -22,9 +22,75 @@ along with GCC; see the file COPYING3. If not see namespace elna::boot { + static char unary_operator_symbol(unary_operator operation) + { + switch (operation) + { + using enum unary_operator; + case reference: + return '@'; + case negation: + case bitwise_negation: + case logical_negation: + return '~'; + case minus: + return '-'; + case plus: + return '+'; + } + __builtin_unreachable(); + } + + static std::string print_binary_operator(const binary_operator operation) + { + switch (operation) + { + using enum binary_operator; + case sum: + return "+"; + case subtraction: + return "-"; + case multiplication: + return "*"; + case division: + return "/"; + case remainder: + return "%"; + case equals: + return "="; + case not_equals: + return "<>"; + case less: + return "<"; + case less_equal: + return "<="; + case greater: + return ">"; + case greater_equal: + return ">="; + case conjunction: + case logical_conjunction: + case bitwise_conjunction: + return "&"; + case disjunction: + case logical_disjunction: + case bitwise_disjunction: + return "or"; + case exclusive_disjunction: + case logical_exclusive_disjunction: + case bitwise_exclusive_disjunction: + return "xor"; + case shift_left: + return "<<"; + case shift_right: + return ">>"; + } + __builtin_unreachable(); + }; + trait_error::trait_error(const source_position position, const std::string& trait_name, payload_type payload) - : error(position), trait_name(trait_name), m_payload(payload) + : error(position), trait_name(trait_name), payload(std::move(payload)) { } @@ -41,35 +107,94 @@ namespace elna::boot + (payload.expected != 1 ? "s" : "") + ", got " + std::to_string(payload.actual); } - else + else if constexpr (std::is_same_v<T, offset_not_field_name>) { - return "The second argument to the #" + this->trait_name + " trait must be a field name"; + return "The second argument to the #" + this->trait_name + + " trait must be a field name"; } - }, this->m_payload); + else if constexpr (std::is_same_v<T, unsupported_type>) + { + return "Type '" + payload.actual.to_string() + + "' does not support trait '#" + this->trait_name + "'"; + } + }, this->payload); } type_mismatch_error::type_mismatch_error(const source_position position, - type expected, type actual) - : error(position), expected(std::move(expected)), actual(std::move(actual)) + type actual, payload_type payload) + : error(position), actual(std::move(actual)), payload(std::move(payload)) { } std::string type_mismatch_error::what() const { - return "Expected type '" + expected.to_string() - + "', but got '" + actual.to_string() + "'"; - } + return std::visit([this](auto&& payload) -> std::string + { + using T = std::decay_t<decltype(payload)>; - constant_assignment_error::constant_assignment_error(const source_position position, - type assignee) - : error(position), assignee(std::move(assignee)) - { - } + if constexpr (std::is_same_v<T, expected_type>) + { + return "Expected type '" + payload.value.to_string() + + "', but got '" + this->actual.to_string() + "'"; + } + else if constexpr (std::is_same_v<T, return_type>) + { + if (!this->actual.empty()) + { + return "Procedure '" + payload.identifier + + "' does not return a value, but return expression has type '" + + this->actual.to_string() + "'"; + } + return "Procedure '" + payload.identifier + + "' is expected to return, but does not have a return statement"; - std::string constant_assignment_error::what() const - { - return "Cannot assign to a value of type '" + assignee.to_string() - + "', because it is constant or contains constant members"; + } + else if constexpr (std::is_same_v<T, unary>) + { + return "Type '" + this->actual.to_string() + "' cannot be used with unary '" + + unary_operator_symbol(payload.operation) + "'"; + } + else if constexpr (std::is_same_v<T, binary>) + { + return "Invalid operands of type '" + this->actual.to_string() + + "' and '" + payload.right.to_string() + + "' for operator " + print_binary_operator(payload.operation); + } + else if constexpr (std::is_same_v<T, invalid_cast>) + { + return "Type '" + this->actual.to_string() + + "' cannot be converted to '" + payload.target.to_string() + "'"; + } + else if constexpr (std::is_same_v<T, kind>) + { + switch (payload) + { + case kind::record_base: + return "Expected a record type, but got '" + + this->actual.to_string() + "'"; + case kind::for_range: + return "Expected an array or slice type, but got '" + + this->actual.to_string() + "'"; + case kind::condition: + return "Condition must be a boolean expression, but got '" + + this->actual.to_string() + "'"; + case kind::constant_assignment: + return "Cannot assign to a value of type '" + this->actual.to_string() + + "', because it is constant or contains constant members"; + case kind::array_index: + return "Array index must be an integral type, but got '" + + this->actual.to_string() + "'"; + case kind::non_indexable: + return "Indexing is not allowed on type '" + + this->actual.to_string() + "'"; + case kind::dereference_of_non_pointer: + return "Type '" + this->actual.to_string() + + "' cannot be dereferenced, it is not a pointer"; + default: + __builtin_unreachable(); + } + } + }, this->payload); } cyclic_declaration_error::cyclic_declaration_error(const std::vector<std::string>& cycle, @@ -91,46 +216,6 @@ namespace elna::boot return message; } - return_error::return_error(const std::string& identifier, const source_position position, - type return_type) - : error(position), identifier(identifier), return_type(std::move(return_type)) - { - } - - std::string return_error::what() const - { - if (!return_type.empty()) - { - return "Procedure '" + this->identifier - + "' does not return a value, but return expression has type '" - + return_type.to_string() + "'"; - } - return "Procedure '" + this->identifier - + "' is expected to return, but does not have a return statement"; - } - - type_kind_error::type_kind_error(const source_position position, kind type_kind, const type& actual) - : error(position), type_kind(type_kind), actual(actual) - { - } - - std::string type_kind_error::what() const - { - switch (this->type_kind) - { - case kind::record_base: - return "'" + this->actual.to_string() + "' is not a record type"; - case kind::for_loop: - return "for-loop variable must be an array or a slice, got '" - + this->actual.to_string() + "'"; - case kind::condition: - return "Condition must be a boolean, got '" - + this->actual.to_string() + "'"; - default: - __builtin_unreachable(); - } - } - argument_count_error::argument_count_error(std::size_t expected, std::size_t actual, const source_position position) : error(position), expected(expected), actual(actual) @@ -151,43 +236,6 @@ namespace elna::boot } } - unary_operation_error::unary_operation_error(const source_position position, - type actual, unary_operator operation) - : error(position), actual(std::move(actual)), op(operation) - { - } - - char unary_operation_error::unary_operator_symbol(unary_operator operation) - { - switch (operation) - { - using enum unary_operator; - case reference: - return '@'; - case negation: - case bitwise_negation: - case logical_negation: - return '~'; - case minus: - return '-'; - case plus: - return '+'; - } - __builtin_unreachable(); - } - - std::string unary_operation_error::what() const - { - return "Type '" + actual.to_string() - + "' cannot be used with unary '" - + unary_operator_symbol(op) + "'"; - } - - /* - * Whether the type itself is constant or has a constant member at any - * nesting level, so that values of this type cannot be reassigned as a - * whole. Pointers to constants do not make the type itself constant. - */ static bool contains_constant_member(const type& checked) { auto referent = resolve_aliases(checked); @@ -248,8 +296,8 @@ namespace elna::boot condition.accept(this); if (!is_primitive_type(condition.type_decoration, "Bool")) { - add_error<type_kind_error>(condition.position(), - type_kind_error::kind::condition, condition.type_decoration); + add_error<type_mismatch_error>(condition.position(), + condition.type_decoration, type_mismatch_error::kind::condition); } } @@ -421,19 +469,22 @@ namespace elna::boot { if (!is_assignable_from(return_type, return_expr->type_decoration)) { - add_error<type_mismatch_error>( - return_expr->position(), return_type, return_expr->type_decoration); + add_error<type_mismatch_error>(return_expr->position(), + return_expr->type_decoration, + type_mismatch_error::expected_type{ return_type }); } } else { - add_error<return_error>(declaration->identifier.name(), - return_expr->position(), return_expr->type_decoration); + add_error<type_mismatch_error>(return_expr->position(), + return_expr->type_decoration, + type_mismatch_error::return_type{ .identifier = declaration->identifier.name() }); } } else if (declaration->heading().return_type.proper_type != nullptr) { - add_error<return_error>(declaration->identifier.name(), declaration->position()); + add_error<type_mismatch_error>(declaration->position(), type(), + type_mismatch_error::return_type{ .identifier = declaration->identifier.name() }); } this->bag.leave(); } @@ -451,13 +502,14 @@ namespace elna::boot if (contains_constant_member(statement->lvalue().type_decoration)) { - add_error<constant_assignment_error>(statement->position(), - statement->lvalue().type_decoration); + add_error<type_mismatch_error>(statement->position(), statement->lvalue().type_decoration, + type_mismatch_error::kind::constant_assignment); } else if (!is_assignable_from(statement->lvalue().type_decoration, statement->rvalue().type_decoration)) { add_error<type_mismatch_error>(statement->position(), - statement->lvalue().type_decoration, statement->rvalue().type_decoration); + statement->rvalue().type_decoration, + type_mismatch_error::expected_type{ statement->lvalue().type_decoration }); } } @@ -475,7 +527,8 @@ namespace elna::boot if (!is_assignable_from(variable_symbol->symbol, declaration->initializer->type_decoration)) { add_error<type_mismatch_error>(declaration->initializer->position(), - variable_symbol->symbol, declaration->initializer->type_decoration); + declaration->initializer->type_decoration, + type_mismatch_error::expected_type{ variable_symbol->symbol }); } } } @@ -491,9 +544,11 @@ namespace elna::boot { if (!is_equality_compatible(condition_type, case_label->type_decoration)) { - add_error<binary_operation_error>( - case_label->position(), condition_type, - case_label->type_decoration, binary_operator::equals); + type_mismatch_error::binary binary_error{ + .right = case_label->type_decoration, + .operation = binary_operator::equals + }; + add_error<type_mismatch_error>(case_label->position(), condition_type, binary_error); } } } @@ -506,8 +561,8 @@ namespace elna::boot if (!get_range_base_type(resolved_range)) { - add_error<type_kind_error>(statement->range().position(), type_kind_error::kind::for_loop, - statement->range().type_decoration); + add_error<type_mismatch_error>(statement->range().position(), + statement->range().type_decoration, type_mismatch_error::kind::for_range); } this->bag.enter(statement->symbols); for (auto *body_statement : statement->body) @@ -594,16 +649,16 @@ namespace elna::boot auto const base_symbol = this->bag.lookup(expression->base.value().name()); if (base_symbol == nullptr || base_symbol->is_type() == nullptr) { - add_error<type_kind_error>(expression->position(), - type_kind_error::kind::record_base, type()); + add_error<type_mismatch_error>(expression->position(), + type(), type_mismatch_error::kind::record_base); } else { type const base_type = resolve_underlying_type(base_symbol->is_type()->symbol); if (base_type.get<record_type>() == nullptr) { - add_error<type_kind_error>(expression->position(), - type_kind_error::kind::record_base, base_type); + add_error<type_mismatch_error>(expression->position(), + base_type, type_mismatch_error::kind::record_base); } } } @@ -626,8 +681,9 @@ namespace elna::boot if (!is_assignable_from(*type_iterator, (*argument_iterator)->type_decoration)) { add_error<type_mismatch_error>( - (*argument_iterator)->position(), *type_iterator, - (*argument_iterator)->type_decoration); + (*argument_iterator)->position(), + (*argument_iterator)->type_decoration, + type_mismatch_error::expected_type{ *type_iterator }); } ++argument_iterator; ++type_iterator; @@ -641,8 +697,8 @@ namespace elna::boot else if (!call->callable().type_decoration.empty()) { add_error<type_mismatch_error>(call->position(), - type(std::make_shared<procedure_type>()), - call->callable().type_decoration); + call->callable().type_decoration, + type_mismatch_error::expected_type{ type(std::make_shared<procedure_type>()) }); } // else callable is not declared which is already reported. } @@ -654,8 +710,8 @@ namespace elna::boot if (record == nullptr) { add_error<type_mismatch_error>( - expression->position(), type(std::make_shared<record_type>()), - expression->type_decoration); + expression->position(), expression->type_decoration, + type_mismatch_error::expected_type{ type(std::make_shared<record_type>()) }); return; } for (const field_initializer& initializer : expression->field_initializers) @@ -667,8 +723,7 @@ namespace elna::boot if (!is_assignable_from(field_type, initializer.value().type_decoration)) { add_error<type_mismatch_error>( - initializer.value().position(), field_type, - initializer.value().type_decoration); + initializer.value().position(), initializer.value().type_decoration, type_mismatch_error::expected_type{field_type}); } break; } @@ -683,8 +738,8 @@ namespace elna::boot if (array == nullptr) { add_error<type_mismatch_error>( - expression->position(), type(std::make_shared<array_type>(type(), 0)), - expression->type_decoration); + expression->position(), expression->type_decoration, + type_mismatch_error::expected_type{ type(std::make_shared<array_type>(type(), 0)) }); return; } if (expression->elements.size() > array->size) @@ -698,7 +753,8 @@ namespace elna::boot if (!is_assignable_from(array->base, element->type_decoration)) { add_error<type_mismatch_error>( - element->position(), array->base, element->type_decoration); + element->position(), element->type_decoration, + type_mismatch_error::expected_type{ array->base }); } } } @@ -708,6 +764,24 @@ namespace elna::boot walking_visitor::visit(expression); } + void type_analysis_visitor::visit(array_access_expression *expression) + { + walking_visitor::visit(expression); + + auto resolved_base = resolve_underlying_type(expression->base().type_decoration); + if (resolved_base.get<array_type>() == nullptr + && resolved_base.get<slice_type>() == nullptr) + { + add_error<type_mismatch_error>(expression->position(), + expression->base().type_decoration, type_mismatch_error::kind::non_indexable); + } + if (!is_integral_type(resolve_underlying_type(expression->index().type_decoration))) + { + add_error<type_mismatch_error>(expression->index().position(), + expression->index().type_decoration, type_mismatch_error::kind::array_index); + } + } + void type_analysis_visitor::visit(unary_expression *expression) { walking_visitor::visit(expression); @@ -719,8 +793,9 @@ namespace elna::boot { if (!is_numeric_type(resolved)) { - add_error<unary_operation_error>(expression->position(), - expression->operand().type_decoration, operation); + add_error<type_mismatch_error>(expression->position(), + expression->operand().type_decoration, + type_mismatch_error::unary{ .operation = operation }); } } else if (operation == unary_operator::minus) @@ -728,8 +803,9 @@ namespace elna::boot if (!is_primitive_type(resolved, "Int") && !is_primitive_type(resolved, "Float")) { - add_error<unary_operation_error>(expression->position(), - expression->operand().type_decoration, operation); + add_error<type_mismatch_error>(expression->position(), + expression->operand().type_decoration, + type_mismatch_error::unary{ .operation = operation }); } } else if (operation == unary_operator::negation) @@ -744,8 +820,9 @@ namespace elna::boot } else { - add_error<unary_operation_error>(expression->position(), - expression->operand().type_decoration, operation); + add_error<type_mismatch_error>(expression->position(), + expression->operand().type_decoration, + type_mismatch_error::unary{ .operation = operation }); } } else if (operation == unary_operator::reference) @@ -753,23 +830,40 @@ namespace elna::boot auto *designator = expression->operand().is_designator(); if (designator == nullptr || designator->is_slicing() != nullptr) { - add_error<unary_operation_error>(expression->position(), - expression->operand().type_decoration, operation); + add_error<type_mismatch_error>(expression->position(), + expression->operand().type_decoration, + type_mismatch_error::unary{ .operation = operation }); } } } - binary_operation_error::binary_operation_error(const source_position position, - type left, type right, binary_operator operation) - : error(position), left(std::move(left)), right(std::move(right)), op(operation) + void type_analysis_visitor::visit(dereference_expression *expression) { + walking_visitor::visit(expression); + + if (resolve_underlying_type(expression->base().type_decoration).get<pointer_type>() == nullptr) + { + add_error<type_mismatch_error>(expression->position(), + expression->base().type_decoration, + type_mismatch_error::kind::dereference_of_non_pointer); + } } - std::string binary_operation_error::what() const + void type_analysis_visitor::visit(cast_expression *expression) { - return "Invalid operands of type '" + left.to_string() - + "' and '" + right.to_string() - + "' for operator " + print_binary_operator(op); + walking_visitor::visit(expression); + + auto source = resolve_underlying_type(expression->value().type_decoration); + auto target = resolve_underlying_type(expression->type_decoration); + + if (source != target // const cast. + && (!is_scalar_type(source) || !is_scalar_type(target)) + && (source.get<slice_type>() == nullptr || target.get<slice_type>() == nullptr)) + { + add_error<type_mismatch_error>(expression->position(), + expression->value().type_decoration, + type_mismatch_error::invalid_cast{ expression->type_decoration }); + } } void type_analysis_visitor::visit(binary_expression *expression) @@ -860,8 +954,11 @@ namespace elna::boot } if (!valid) { - add_error<binary_operation_error>(expression->position(), - expression->lhs().type_decoration, expression->rhs().type_decoration, operation); + type_mismatch_error::binary binary_error{ + .right = expression->rhs().type_decoration, + .operation = operation + }; + add_error<type_mismatch_error>(expression->position(), expression->lhs().type_decoration, binary_error); } } @@ -869,8 +966,7 @@ namespace elna::boot { walking_visitor::visit(trait); - if (trait->name == "size" || trait->name == "alignment" - || trait->name == "min" || trait->name == "max") + if (trait->name == "size" || trait->name == "alignment") { if (trait->arguments.size() != 1) { @@ -878,6 +974,26 @@ namespace elna::boot trait_error::argument_count{ .expected = 1, .actual = trait->arguments.size() }); } } + else if ((trait->name == "min" || trait->name == "max") && !trait->type_decoration.empty()) + { + if (trait->arguments.size() != 1) + { + add_error<trait_error>(trait->position(), trait->name.name(), + trait_error::argument_count{ .expected = 1, .actual = trait->arguments.size() }); + } + else + { + const type resolved = resolve_underlying_type(trait->type_decoration); + + if (resolved.get<enumeration_type>() == nullptr + && !is_primitive_type(resolved, "Float") + && !is_discrete_type(resolved)) + { + add_error<trait_error>(trait->name.position(), trait->name.name(), + trait_error::unsupported_type{ trait->type_decoration }); + } + } + } else if (trait->name == "offset") { if (trait->arguments.size() != 2) |
