commit 154a61d7fb282ad610cbf72c865c1275535b2699
parent 8fcadc2d6e54c377ca8e816c36b60ec5f3a64709
Author: Bor Grošelj Simić <bgs@turminal.net>
Date: Thu, 2 Jun 2022 00:33:46 +0200
fix issues with some enum declaration orders
Depending on the order of declarations, enum values in enum aliases
sometimes didn't get created correctly resulting in "unknown object"
errors.
This commit fixes the issue by storing a map (incomplete enum alias value ->
enum) for each value separately instead of just (enum -> enum alias)
maps.
Signed-off-by: Bor Grošelj Simić <bgs@turminal.net>
Diffstat:
4 files changed, 72 insertions(+), 45 deletions(-)
diff --git a/docs/declaration_solver.txt b/docs/declaration_solver.txt
@@ -13,20 +13,27 @@ variables and enum fields.
First, the constants defined upon harec invocation are checked and put into a
dedicated scope. Then the imports for each subunit are loaded. This step requires
the command line defines to already be in place. After the imports of a
-subunit are loaded, all of its declarations are marked incomplete and put into
-the unit scope. Duplicate declarations are caught and reported at this step.
-Special care needs to be taken with enums, by creating a scope for each of them
-and inserting all the fields into it in the correct order.
+subunit are loaded, all of its declarations except enums are marked
+incomplete and put into the unit scope. Duplicate declarations are caught and
+reported at this step. Enum types are treated separately from enum values
+in this algorithm. Enum types never have dependencies and can be completed
+on the spot. Enum values are put into a special scope that is created for each
+enum type and marked incomplete.
At this point the defines can be copied from the dedicated scope into the unit
scope, shadowing the declarations from the source files.
-Next, aliases of enum types are detected and taken care of. A list of aliases
-is build for each declared enum, so that in the process when enum values are
-completed, the same value is declared in enum aliases' namespaces too.
-Additionally, every enum value that is already completed (the ones that were
-imported from another module) gets instantly copied to each enum alias'
-namespace.
+Next, aliases of enum types are detected and taken care of. Because an enum
+alias only depends on the underlying enum type, its entire dependency tree
+is known immediately when it is detected, so it can be completed right away.
+For values of enum aliases we need to cover three possible configurations:
+ (a) An alias to an enum whose values are already completed at this
+ stage. This happens when the underlying enum was imported from
+ another module. This is the easiest case to handle, we can just
+ copy it's values to the alias immediately.
+ (b) An enum alias whose underlying enum is defined in the same unit
+ this case is handled by the general resolution algorithm described
+ below.
With everything but the declarations in place, the core part of the algorithm
is started:
diff --git a/include/types.h b/include/types.h
@@ -73,7 +73,6 @@ struct type_enum_value {
struct type_enum {
struct scope *values;
- struct identifiers *aliases;
};
enum variadism {
diff --git a/src/check.c b/src/check.c
@@ -3281,10 +3281,14 @@ incomplete_declaration_create(struct context *ctx, struct location loc,
struct scope *scope, const struct identifier *ident,
const struct identifier *name)
{
+ struct scope *subunit = ctx->unit->parent;
+ ctx->unit->parent = NULL;
struct incomplete_declaration *idecl =
(struct incomplete_declaration *)scope_lookup(scope, name);
+ ctx->unit->parent = subunit;
+
if (idecl) {
- error(ctx, loc, NULL, "Duplicate global identifier '%s'",
+ expect(&loc, NULL, "Duplicate global identifier '%s'",
identifier_unparse(ident));
return idecl;
}
@@ -3579,20 +3583,6 @@ resolve_enum_field(struct context *ctx, const struct scope_object *obj)
}
ctx->resolving_enum = NULL;
- for (struct identifiers *id = type->_enum.aliases; id; id = id->next) {
- const struct scope_object *obj = scope_lookup(ctx->scope, &id->ident);
- if (obj->otype == O_SCAN) {
- obj = wrap_resolver(ctx, obj, resolve_decl);
- }
- struct identifier alias_ident, alias_name = {
- .name = name.name,
- .ns = &id->ident,
- };
- mkident(ctx, &alias_ident, &alias_name);
- scope_insert(ctx->scope, O_CONST, &alias_ident,
- &alias_name, obj->type, value);
- }
-
return scope_insert(idecl->field->enum_scope, O_CONST, &name, &localname, type, value);
}
@@ -3600,8 +3590,8 @@ const struct scope_object *
resolve_enum_alias(struct context *ctx, const struct scope_object *obj)
{
struct incomplete_declaration *idecl;
- struct identifier alias, sub;
- identifier_dup(&alias, &obj->name);
+ struct identifier sub;
+ const struct scope_object *orig = obj;
switch (obj->otype) {
case O_SCAN:
idecl = (struct incomplete_declaration *)obj;
@@ -3654,24 +3644,48 @@ resolve_enum_alias(struct context *ctx, const struct scope_object *obj)
}
assert(obj->otype == O_TYPE && obj->type->storage == STORAGE_ENUM);
- struct identifiers *new = xcalloc(1, sizeof(struct identifiers));
- identifier_dup(&new->ident, &alias);
- new->next = obj->type->_enum.aliases;
- ((struct type *)obj->type)->_enum.aliases = new;
- const struct type *type = type_dealias(obj->type);
+ // orig->type is (perhaps transitively) an alias of a resolved enum
+ // type, which means its dependency graph is a linear chain of
+ // resolved types ending with that enum, so we can immediately resolve it
+ // There's no need to wrap this call, because the context is already
+ // correct
+ const struct type *type = resolve_type(ctx, orig)->type;
+
for (const struct scope_object *val = obj->type->_enum.values->objects;
val; val = val->lnext) {
- if (val->otype == O_SCAN) {
- continue;
- }
+ struct identifier ns;
+ identifier_dup(&ns, &orig->name);
struct identifier ident, name = {
.name = val->name.name,
- .ns = &alias,
+ .ns = &ns,
};
mkident(ctx, &ident, &name);
- scope_insert(ctx->scope, O_CONST, &ident, &name,
+ if (val->otype != O_SCAN) {
+ scope_insert(ctx->scope, O_CONST, &ident, &name,
type, val->value);
- }
+ continue;
+ }
+ struct ast_enum_field *afield =
+ xcalloc(1, sizeof(struct ast_enum_field));
+ *afield = (struct ast_enum_field){
+ .loc = (struct location){0}, // XXX: what to put here?
+ .name = strdup(val->name.name),
+ };
+
+ struct incomplete_enum_field *field =
+ xcalloc(1, sizeof(struct incomplete_enum_field));
+ idecl = (struct incomplete_declaration *)val;
+ *field = (struct incomplete_enum_field){
+ .field = afield,
+ .type = type,
+ .enum_scope = idecl->field->enum_scope,
+ };
+
+ idecl = incomplete_declaration_create(ctx, (struct location){0},
+ ctx->scope, &ident, &name);
+ idecl->type = IDECL_ENUM_FLD;
+ idecl->field = field;
+ };
return obj;
}
diff --git a/tests/15-enums.ha b/tests/15-enums.ha
@@ -99,18 +99,25 @@ fn interdependent() void = {
assert(interdependent2::C == 5);
};
-type alias = with_storage;
+type e1 = enum { a, b, c, d }, a1 = e1;
+type a2 = e2, e2 = enum u8 { a, b, c, d }; // reverse
type imported_alias = testmod::_enum;
type imported_double_alias = testmod::enum_alias;
fn aliases() void = {
- assert(size(alias) == size(with_storage));
- assert(alias::CAFE == with_storage::CAFE);
- assert(alias::BABE == with_storage::BABE);
- assert(alias::DEAD == with_storage::DEAD);
- assert(alias::BEEF == with_storage::BEEF);
+ assert(size(a1) == size(e1));
+ assert(a1::a == e1::a);
+ assert(a1::b == e1::b);
+ assert(a1::c == e1::c);
+ assert(a1::d == e1::d);
+
+ assert(size(a2) == size(e2));
+ assert(a2::a == e2::a);
+ assert(a2::b == e2::b);
+ assert(a2::c == e2::c);
+ assert(a2::d == e2::d);
// test with alias of imported enum
assert(imported_alias::ONE == testmod::_enum::ONE);