aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--boot/ast.cc19
-rw-r--r--boot/name_analysis.cc5
-rw-r--r--boot/parser.yy5
-rw-r--r--boot/symbol.cc9
-rw-r--r--boot/type_check.cc226
-rw-r--r--gcc/gcc/elna-builtins.cc9
-rw-r--r--gcc/gcc/elna-generic.cc7
-rw-r--r--include/elna/boot/ast.h12
-rw-r--r--include/elna/boot/name_analysis.h1
-rw-r--r--include/elna/boot/symbol.h13
-rw-r--r--include/elna/boot/type_check.h9
-rw-r--r--source/cstdio.elna2
-rw-r--r--testsuite/compilable/opaque_type.elna14
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.