diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-08-23 14:05:26 +0200 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-08-23 14:05:26 +0200 |
| commit | bf41d022ce9736a92097f416ab96fca7ab4594ca (patch) | |
| tree | 74813da62ec329f826828d2cac8701a94277dc41 | |
| parent | e4fece8e791cddec1c765c9834e1c13d0f258e16 (diff) | |
| download | elna-bf41d022ce9736a92097f416ab96fca7ab4594ca.tar.gz | |
Reject 0-sized variables
| -rw-r--r-- | boot/ast.cc | 19 | ||||
| -rw-r--r-- | boot/name_analysis.cc | 5 | ||||
| -rw-r--r-- | boot/parser.yy | 5 | ||||
| -rw-r--r-- | boot/symbol.cc | 9 | ||||
| -rw-r--r-- | boot/type_check.cc | 226 | ||||
| -rw-r--r-- | gcc/gcc/elna-builtins.cc | 9 | ||||
| -rw-r--r-- | gcc/gcc/elna-generic.cc | 7 | ||||
| -rw-r--r-- | include/elna/boot/ast.h | 12 | ||||
| -rw-r--r-- | include/elna/boot/name_analysis.h | 1 | ||||
| -rw-r--r-- | include/elna/boot/symbol.h | 13 | ||||
| -rw-r--r-- | include/elna/boot/type_check.h | 9 | ||||
| -rw-r--r-- | source/cstdio.elna | 2 | ||||
| -rw-r--r-- | testsuite/compilable/opaque_type.elna | 14 |
13 files changed, 323 insertions, 8 deletions
diff --git a/boot/ast.cc b/boot/ast.cc index 287b7aa..1f2b884 100644 --- a/boot/ast.cc +++ b/boot/ast.cc @@ -61,6 +61,11 @@ namespace elna::boot __builtin_unreachable(); } + void empty_visitor::visit(extern_type_expression *) + { + __builtin_unreachable(); + } + void empty_visitor::visit(variable_declaration *) { __builtin_unreachable(); @@ -436,6 +441,10 @@ namespace elna::boot { } + void walking_visitor::visit(extern_type_expression *) + { + } + void walking_visitor::visit(cast_expression *expression) { expression->value().accept(this); @@ -731,6 +740,16 @@ namespace elna::boot return this; } + extern_type_expression::extern_type_expression(const source_position position) + : node(position) + { + } + + void extern_type_expression::accept(parser_visitor *visitor) + { + visitor->visit(this); + } + type_expression& constant_type_expression::base() { return *m_base; diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc index 1ad66a8..d4b8643 100644 --- a/boot/name_analysis.cc +++ b/boot/name_analysis.cc @@ -507,6 +507,11 @@ namespace elna::boot this->current_type = type(result_type); } + void name_analysis_visitor::visit(extern_type_expression *) + { + this->current_type = type(std::make_shared<extern_type>()); + } + void name_analysis_visitor::visit(enumeration_type_expression *expression) { std::vector<std::string> member_names; diff --git a/boot/parser.yy b/boot/parser.yy index c45fdbc..0c32610 100644 --- a/boot/parser.yy +++ b/boot/parser.yy @@ -643,6 +643,11 @@ type_declaration: identifier_definition "=" type_expression { $$ = new boot::type_declaration(boot::make_position(@$), std::move(*$1), $3); } + | identifier_definition "=" "extern" + { + $$ = new boot::type_declaration(boot::make_position(@$), std::move(*$1), + new boot::extern_type_expression(boot::make_position(@$))); + } type_declarations: type_declaration type_declarations { diff --git a/boot/symbol.cc b/boot/symbol.cc index fdf6a65..b32782e 100644 --- a/boot/symbol.cc +++ b/boot/symbol.cc @@ -55,6 +55,7 @@ namespace elna::boot template std::shared_ptr<constant_type> type::get<constant_type>() const; template std::shared_ptr<procedure_type> type::get<procedure_type>() const; template std::shared_ptr<enumeration_type> type::get<enumeration_type>() const; + template std::shared_ptr<extern_type> type::get<extern_type>() const; bool type::operator==(const std::nullptr_t&) const { @@ -80,6 +81,10 @@ namespace elna::boot { return left_enumeration == resolved_that.get<enumeration_type>(); } + if (auto left_extern = resolved_this.get<extern_type>()) + { + return left_extern == resolved_that.get<extern_type>(); + } if (auto left_pointer = resolved_this.get<pointer_type>()) { auto right_pointer = resolved_that.get<pointer_type>(); @@ -181,6 +186,10 @@ namespace elna::boot { return "(enumeration)"; } + else if constexpr (std::is_same_v<T, std::shared_ptr<extern_type>>) + { + return "extern"; + } }, payload); } diff --git a/boot/type_check.cc b/boot/type_check.cc index 2d82579..e810e30 100644 --- a/boot/type_check.cc +++ b/boot/type_check.cc @@ -16,6 +16,7 @@ along with GCC; see the file COPYING3. If not see <http://www.gnu.org/licenses/>. */ #include "elna/boot/type_check.h" +#include "elna/boot/evaluator.h" #include <algorithm> #include <utility> @@ -195,6 +196,27 @@ namespace elna::boot case dereference_of_non_pointer: return "Type '" + this->actual.to_string() + "' cannot be dereferenced, it is not a pointer"; + case opaque_variable: + return "Opaque type '" + this->actual.to_string() + + "' cannot be used to declare a variable"; + case opaque_parameter: + return "Opaque type '" + this->actual.to_string() + + "' cannot be used as a procedure parameter"; + case opaque_return: + return "Opaque type '" + this->actual.to_string() + + "' cannot be used as a return type"; + case opaque_field: + return "Opaque type '" + this->actual.to_string() + + "' cannot be used as a record field type"; + case opaque_element: + return "Opaque type '" + this->actual.to_string() + + "' cannot be used as an array or slice element type"; + case opaque_cast: + return "Opaque type '" + this->actual.to_string() + + "' cannot be used as a cast target type"; + case zero_sized: + return "Zero-sized type '" + this->actual.to_string() + + "' cannot be declared"; default: __builtin_unreachable(); } @@ -277,6 +299,82 @@ namespace elna::boot return false; } + /* + * Finds the first opaque type in a value position, following aliases, + * qualifiers, arrays, slices, records and procedures but not pointers. + */ + static std::optional<type> find_opaque_type(const type& checked) + { + if (resolve_underlying_type(checked).get<extern_type>() != nullptr) + { + return checked; + } + const type referent = resolve_aliases(checked); + + if (auto record = referent.get<record_type>()) + { + for (const auto& [field_name, field_type] : record->fields) + { + if (auto opaque = find_opaque_type(field_type)) + { + return opaque; + } + } + return !record->base.empty() ? find_opaque_type(record->base) : std::nullopt; + } + else if (auto array = referent.get<array_type>()) + { + return find_opaque_type(array->base); + } + else if (auto slice = referent.get<slice_type>()) + { + return find_opaque_type(slice->base); + } + else if (auto procedure = referent.get<procedure_type>()) + { + for (const type& parameter : procedure->parameters) + { + if (auto opaque = find_opaque_type(parameter)) + { + return opaque; + } + } + return !procedure->return_type.proper_type.empty() + ? find_opaque_type(procedure->return_type.proper_type) + : std::nullopt; + } + return std::nullopt; + } + + // Checks whether the type has zero size. + static bool has_zero_size(const type& checked, const target_info& target) + { + auto properties = get_type_properties(resolve_underlying_type(checked), target); + + return properties.has_value() && properties->size == 0; + } + + // Finds the first zero-sized type: the type itself or a record field. + // Bases, pointers, arrays and slices are not entered. + static std::optional<type> find_zero_sized(const type& checked, const target_info& target) + { + if (has_zero_size(checked, target)) + { + return checked; + } + if (auto record = resolve_aliases(checked).get<record_type>()) + { + for (const auto& [field_name, field_type] : record->fields) + { + if (auto found = find_zero_sized(field_type, target)) + { + return found; + } + } + } + return std::nullopt; + } + bool type_analysis_visitor::is_equality_compatible(const type& left, const type& right) { auto resolved_left = resolve_underlying_type(left); @@ -479,6 +577,39 @@ namespace elna::boot } walking_visitor::visit(declaration); + std::size_t parameter_index = 0; + + for (const type& parameter : this->current_procedure->symbol.parameters) + { + if (auto opaque = find_opaque_type(parameter)) + { + add_error<type_requirement_error>( + declaration->heading().parameters.at(parameter_index).second->position(), + opaque.value(), type_requirement_error::kind::opaque_parameter); + } + else if (auto found = find_zero_sized(parameter, this->target)) + { + add_error<type_requirement_error>( + declaration->heading().parameters.at(parameter_index).second->position(), + found.value(), type_requirement_error::kind::zero_sized); + } + ++parameter_index; + } + if (declaration->heading().return_type.proper_type != nullptr) + { + if (auto opaque = find_opaque_type(this->current_procedure->symbol.return_type.proper_type)) + { + add_error<type_requirement_error>( + declaration->heading().return_type.proper_type->position(), + opaque.value(), type_requirement_error::kind::opaque_return); + } + else if (auto found = find_zero_sized(this->current_procedure->symbol.return_type.proper_type, this->target)) + { + add_error<type_requirement_error>( + declaration->heading().return_type.proper_type->position(), + found.value(), type_requirement_error::kind::zero_sized); + } + } if (declaration->body.has_value()) { if (declaration->body.value().return_expression != nullptr) @@ -538,6 +669,21 @@ namespace elna::boot { walking_visitor::visit(declaration); + for (const identifier_definition& variable_identifier : declaration->identifiers) + { + auto variable_symbol = this->bag.lookup(variable_identifier.name())->is_variable(); + + if (auto opaque = find_opaque_type(variable_symbol->symbol)) + { + add_error<type_requirement_error>(variable_identifier.id().position(), + opaque.value(), type_requirement_error::kind::opaque_variable); + } + else if (auto found = find_zero_sized(variable_symbol->symbol, this->target)) + { + add_error<type_requirement_error>(variable_identifier.id().position(), + found.value(), type_requirement_error::kind::zero_sized); + } + } if (declaration->initializer == nullptr || has_errors()) { return; @@ -661,6 +807,70 @@ namespace elna::boot else { walking_visitor::visit(declaration); + const type referent = resolve_aliases(unresolved_type->referent); + + if (auto record = referent.get<record_type>()) + { + for (const auto& [field_name, field_type] : record->fields) + { + if (auto opaque = find_opaque_type(field_type)) + { + add_error<type_requirement_error>(declaration->position(), + opaque.value(), type_requirement_error::kind::opaque_field); + } + else if (has_zero_size(field_type, this->target)) + { + add_error<type_requirement_error>(declaration->position(), + field_type, type_requirement_error::kind::zero_sized); + } + } + } + else if (auto array = referent.get<array_type>()) + { + if (auto opaque = find_opaque_type(array->base)) + { + add_error<type_requirement_error>(declaration->position(), + opaque.value(), type_requirement_error::kind::opaque_element); + } + } + else if (auto slice = referent.get<slice_type>()) + { + if (auto opaque = find_opaque_type(slice->base)) + { + add_error<type_requirement_error>(declaration->position(), + opaque.value(), type_requirement_error::kind::opaque_element); + } + } + else if (auto procedure = referent.get<procedure_type>()) + { + for (const type& parameter : procedure->parameters) + { + if (auto opaque = find_opaque_type(parameter)) + { + add_error<type_requirement_error>(declaration->position(), + opaque.value(), type_requirement_error::kind::opaque_parameter); + } + else if (has_zero_size(parameter, this->target)) + { + add_error<type_requirement_error>(declaration->position(), + parameter, type_requirement_error::kind::zero_sized); + } + } + if (!procedure->return_type.proper_type.empty()) + { + if (auto opaque = find_opaque_type(procedure->return_type.proper_type)) + { + add_error<type_requirement_error>(declaration->position(), + opaque.value(), type_requirement_error::kind::opaque_return); + } + else if (has_zero_size(procedure->return_type.proper_type, this->target)) + { + add_error<type_requirement_error>(declaration->position(), + procedure->return_type.proper_type, + type_requirement_error::kind::zero_sized); + } + } + } } } @@ -886,6 +1096,12 @@ namespace elna::boot { walking_visitor::visit(expression); + if (auto opaque = find_opaque_type(expression->type_decoration)) + { + add_error<type_requirement_error>(expression->position(), + opaque.value(), type_requirement_error::kind::opaque_cast); + return; + } auto source = resolve_underlying_type(expression->value().type_decoration); auto target = resolve_underlying_type(expression->type_decoration); @@ -1009,6 +1225,11 @@ namespace elna::boot argument_count_error::kind::trait, trait->name.name(), 1, trait->arguments.size()); } + else if (find_opaque_type(trait->types.front())) + { + add_error<trait_error>(trait->name.position(), trait->name.name(), + trait_error::unsupported_type{ trait->types.front() }); + } } else if ((trait->name == "min" || trait->name == "max") && !trait->type_decoration.empty()) { @@ -1039,6 +1260,11 @@ namespace elna::boot argument_count_error::kind::trait, trait->name.name(), 2, trait->arguments.size()); } + else if (find_opaque_type(trait->types.front())) + { + add_error<trait_error>(trait->name.position(), trait->name.name(), + trait_error::unsupported_type{ trait->types.front() }); + } else if (trait->arguments.at(1)->is_named() == nullptr) { add_error<trait_error>(trait->arguments.at(1)->position(), trait->name.name(), diff --git a/gcc/gcc/elna-builtins.cc b/gcc/gcc/elna-builtins.cc index 9552abb..c728a00 100644 --- a/gcc/gcc/elna-builtins.cc +++ b/gcc/gcc/elna-builtins.cc @@ -206,6 +206,15 @@ namespace elna::gcc { return build_enumeration_type(reference->members); } + else if (auto reference = type.get<boot::extern_type>()) + { + /* + * An incomplete type: a record without fields and without layout, + * mirroring a forward-declared C structure. Opaque types can only + * occur behind pointers, so no size or field is ever requested. + */ + return make_node(RECORD_TYPE); + } else if (auto reference = type.get<boot::pointer_type>()) { return build_pointer_type(get_inner_alias(reference->base, symbols)); diff --git a/gcc/gcc/elna-generic.cc b/gcc/gcc/elna-generic.cc index fb898d5..b657e92 100644 --- a/gcc/gcc/elna-generic.cc +++ b/gcc/gcc/elna-generic.cc @@ -525,11 +525,8 @@ namespace elna::gcc { auto value = this->constant_evaluator.evaluate_traits(*trait); - if (!value.has_value()) - { - this->current_expression = error_mark_node; - return; - } + // Type checking rejects every trait invocation that the evaluator cannot fold. + gcc_assert(value.has_value()); tree type = get_inner_alias(trait->type_decoration, this->symbols); this->current_expression = constant_to_tree(value.value(), this->symbols, type); } diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h index 8425b54..94488ff 100644 --- a/include/elna/boot/ast.h +++ b/include/elna/boot/ast.h @@ -138,6 +138,7 @@ namespace elna::boot class record_type_expression; class procedure_type_expression; class enumeration_type_expression; + class extern_type_expression; class named_expression; class array_access_expression; class field_access_expression; @@ -182,6 +183,7 @@ namespace elna::boot virtual void visit(record_type_expression *) = 0; virtual void visit(procedure_type_expression *) = 0; virtual void visit(enumeration_type_expression *) = 0; + virtual void visit(extern_type_expression *) = 0; virtual void visit(named_expression *) = 0; virtual void visit(array_access_expression *) = 0; virtual void visit(field_access_expression *) = 0; @@ -210,6 +212,7 @@ namespace elna::boot [[noreturn]] void visit(record_type_expression *) override; [[noreturn]] void visit(procedure_type_expression *) override; [[noreturn]] void visit(enumeration_type_expression *) override; + [[noreturn]] void visit(extern_type_expression *) override; [[noreturn]] void visit(variable_declaration *) override; [[noreturn]] void visit(procedure_declaration *) override; @@ -257,6 +260,7 @@ namespace elna::boot void visit(record_type_expression *) override; void visit(procedure_type_expression *) override; void visit(enumeration_type_expression *) override; + void visit(extern_type_expression *) override; void visit(variable_declaration *) override; void visit(procedure_declaration *) override; @@ -422,6 +426,14 @@ namespace elna::boot type_expression& base(); }; + class extern_type_expression : public type_expression + { + public: + explicit extern_type_expression(const source_position position); + + void accept(parser_visitor *visitor) override; + }; + using field_declaration = std::pair<std::vector<identifier>, std::shared_ptr<type_expression>>; class record_type_expression : public type_expression diff --git a/include/elna/boot/name_analysis.h b/include/elna/boot/name_analysis.h index 50d9fe6..f2e2eb9 100644 --- a/include/elna/boot/name_analysis.h +++ b/include/elna/boot/name_analysis.h @@ -155,6 +155,7 @@ namespace elna::boot void visit(slicing_expression *expression) override; void visit(procedure_type_expression *expression) override; void visit(enumeration_type_expression *expression) override; + void visit(extern_type_expression *) override; void visit(variable_declaration *declaration) override; void visit(procedure_declaration *declaration) override; diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h index 1caba83..0c1026d 100644 --- a/include/elna/boot/symbol.h +++ b/include/elna/boot/symbol.h @@ -40,6 +40,7 @@ namespace elna::boot struct slice_type; struct procedure_type; struct enumeration_type; + struct extern_type; /** * Represents a type stored in the symbol table. @@ -59,7 +60,8 @@ namespace elna::boot std::shared_ptr<array_type>, std::shared_ptr<slice_type>, std::shared_ptr<procedure_type>, - std::shared_ptr<enumeration_type> + std::shared_ptr<enumeration_type>, + std::shared_ptr<extern_type> >; Payload payload; @@ -188,6 +190,15 @@ namespace elna::boot explicit enumeration_type(const std::vector<std::string>& members); }; + /** + * An opaque type declared with \c type T = extern. The representation is + * unknown, so the type has no properties and can only be used behind a + * pointer. + */ + struct extern_type + { + }; + class type_info; class procedure_info; class variable_info; diff --git a/include/elna/boot/type_check.h b/include/elna/boot/type_check.h index 7a8eece..0b62a4a 100644 --- a/include/elna/boot/type_check.h +++ b/include/elna/boot/type_check.h @@ -86,7 +86,14 @@ namespace elna::boot condition, array_index, non_indexable, - dereference_of_non_pointer + dereference_of_non_pointer, + opaque_variable, + opaque_parameter, + opaque_return, + opaque_field, + opaque_element, + opaque_cast, + zero_sized }; type_requirement_error(const source_position position, diff --git a/source/cstdio.elna b/source/cstdio.elna index cd218fe..78c3a24 100644 --- a/source/cstdio.elna +++ b/source/cstdio.elna @@ -3,7 +3,7 @@ obtain one at https://mozilla.org/MPL/2.0/. *) type - FILE* = record end + FILE* = extern var stdin*: ^FILE := extern diff --git a/testsuite/compilable/opaque_type.elna b/testsuite/compilable/opaque_type.elna new file mode 100644 index 0000000..b46cf68 --- /dev/null +++ b/testsuite/compilable/opaque_type.elna @@ -0,0 +1,14 @@ +type + Handle = extern + Alias = Handle + +var + handle: ^Handle := nil + alias: ^Alias + +proc take(h: ^Handle): ^Handle +return h + +begin + alias := take(handle) +end. |
