commit 61ea954772ceb838326bb5275635961c0ead460e
parent cd8e44cbb0d975160cf6bd755798793e080a164c
Author: Bor Grošelj Simić <bgs@turminal.net>
Date: Thu, 25 Aug 2022 01:26:50 +0200
unify type validity checks for tagged unions
Signed-off-by: Bor Grošelj Simić <bgs@turminal.net>
Diffstat:
4 files changed, 84 insertions(+), 26 deletions(-)
diff --git a/include/type_store.h b/include/type_store.h
@@ -20,7 +20,7 @@ struct type_store {
// Applies the type reduction algorithm to the given tagged union.
const struct type *type_store_reduce_result(struct type_store *store,
- struct type_tagged_union *in);
+ struct location loc, struct type_tagged_union *in);
struct ast_type;
@@ -49,7 +49,7 @@ const struct type *type_store_lookup_alias(struct type_store *store,
const struct type *secondary);
const struct type *type_store_lookup_tagged(struct type_store *store,
- struct type_tagged_union *tags);
+ struct location loc, struct type_tagged_union *tags);
// Returns a (non-tagged) union of the members of a tagged union type
const struct type *type_store_tagged_to_union(
diff --git a/src/check.c b/src/check.c
@@ -1604,7 +1604,8 @@ check_expr_compound(struct context *ctx,
}
expr->terminates = lexpr->terminates && lexpr->type != EXPR_YIELD;
- expr->result = type_store_reduce_result(ctx->store, scope->results);
+ expr->result = type_store_reduce_result(ctx->store, aexpr->loc,
+ scope->results);
for (struct yield *yield = scope->yields; yield;) {
struct expression *lowered = lower_implicit_cast(
@@ -1916,7 +1917,8 @@ check_expr_if(struct context *ctx,
.next = &_tags,
};
expr->result =
- type_store_reduce_result(ctx->store, &tags);
+ type_store_reduce_result(ctx->store, aexpr->loc,
+ &tags);
if (expr->result == NULL) {
error(ctx, aexpr->loc, expr,
"Invalid result type (dangling or ambiguous null)");
@@ -2064,7 +2066,7 @@ check_expr_match(struct context *ctx,
expr->result = hint;
} else {
expr->result = type_store_reduce_result(
- ctx->store, &result_type);
+ ctx->store, aexpr->loc, &result_type);
if (expr->result == NULL) {
error(ctx, aexpr->loc, expr,
"Invalid result type (dangling or ambiguous null)");
@@ -2233,7 +2235,7 @@ check_expr_propagate(struct context *ctx,
const struct type *return_type;
if (return_tagged.next) {
return_type = type_store_lookup_tagged(
- ctx->store, &return_tagged);
+ ctx->store, aexpr->loc, &return_tagged);
} else {
return_type = return_tagged.type;
}
@@ -2243,7 +2245,7 @@ check_expr_propagate(struct context *ctx,
result_type = &builtin_type_void;
} else if (result_tagged.next) {
result_type = type_store_lookup_tagged(
- ctx->store, &result_tagged);
+ ctx->store, aexpr->loc, &result_tagged);
} else {
result_type = result_tagged.type;
}
@@ -2740,7 +2742,7 @@ check_expr_switch(struct context *ctx,
expr->result = hint;
} else {
expr->result = type_store_reduce_result(
- ctx->store, &result_type);
+ ctx->store, aexpr->loc, &result_type);
if (expr->result == NULL) {
error(ctx, aexpr->loc, expr,
"Invalid result type (dangling or ambiguous null)");
diff --git a/src/type_store.c b/src/type_store.c
@@ -305,6 +305,33 @@ struct_init_from_atype(struct type_store *store, enum type_storage storage,
}
}
+static bool
+enforce_tagged_invariants(struct type_store *store, struct location loc,
+ const struct type *type)
+{
+ int i;
+ const struct type_tagged_union *tu;
+ for (i = 0, tu = &type->tagged; tu; i++, tu = tu->next) {
+ if (tu->type->storage == STORAGE_NULL) {
+ error(store->check_context, loc,
+ "Null type not allowed in this context");
+ return false;
+ }
+ if (tu->type->size == SIZE_UNDEFINED) {
+ error(store->check_context, loc,
+ "Type of undefined size is not a valid tagged union member");
+ return false;
+ }
+ assert(tu->type->align != ALIGN_UNDEFINED);
+ }
+ if (i <= 1) {
+ error(store->check_context, loc,
+ "Tagged unions must have at least two distinct members");
+ return false;
+ }
+ return true;
+}
+
static size_t
sum_tagged_memb(struct type_store *store,
const struct type_tagged_union *u)
@@ -404,8 +431,7 @@ collect_atagged_memb(struct type_store *store,
size_t *i)
{
for (; atu; atu = atu->next) {
- const struct type *type =
- lookup_atype(store, atu->type);
+ const struct type *type = lookup_atype(store, atu->type);
if (type->storage == STORAGE_TAGGED) {
collect_tagged_memb(store, ta, &type->tagged, i);
continue;
@@ -428,7 +454,7 @@ tagged_cmp(const void *ptr_a, const void *ptr_b)
: (*a)->type->id > (*b)->type->id ? 1 : 0;
}
-static size_t
+static void
tagged_init(struct type_store *store, struct type *type,
struct type_tagged_union **tu, size_t nmemb)
{
@@ -468,7 +494,6 @@ tagged_init(struct type_store *store, struct type *type,
}
type->size += builtin_type_uint.size % type->align
+ builtin_type_uint.align;
- return nmemb;
}
static void
@@ -480,12 +505,10 @@ tagged_init_from_atype(struct type_store *store,
xcalloc(nmemb, sizeof(struct type_tagged_union *));
size_t i = 0;
collect_atagged_memb(store, tu, &atype->tagged_union, &i);
- nmemb = tagged_init(store, type, tu, nmemb);
-
- if (nmemb <= 1) {
- error(store->check_context, atype->loc,
- "Cannot create tagged union with a single member");
- }
+ tagged_init(store, type, tu, nmemb);
+ if (!enforce_tagged_invariants(store, atype->loc, type)) {
+ *type = builtin_type_void;
+ };
}
static struct dimensions
@@ -512,6 +535,11 @@ _tagged_size(struct type_store *store, const struct ast_tagged_union_type *u)
} else {
memb = lookup_atype_with_dimensions(store, NULL, atype);
}
+ if (memb.size == SIZE_UNDEFINED) {
+ error(store->check_context, atype->loc,
+ "Type of undefined size is not a valid tagged union member");
+ return (struct dimensions){0};
+ }
if (dim.size < memb.size) {
dim.size = memb.size;
}
@@ -978,9 +1006,10 @@ type_store_lookup_alias(struct type_store *store, const struct type *type)
}
-const struct type *
-type_store_lookup_tagged(struct type_store *store,
- struct type_tagged_union *tags)
+// Sorts members by id and deduplicates entries. Does not enforce usual tagged
+// union invariants. The returned type is not a singleton.
+static const struct type *
+lookup_tagged(struct type_store *store, struct type_tagged_union *tags)
{
struct type type = {
.storage = STORAGE_TAGGED,
@@ -991,7 +1020,20 @@ type_store_lookup_tagged(struct type_store *store,
size_t i = 0;
collect_tagged_memb(store, tu, tags, &i);
tagged_init(store, &type, tu, nmemb);
- return type_store_lookup_type(store, &type);
+ struct type *ret = xcalloc(1, sizeof(struct type));
+ *ret = type;
+ return ret;
+}
+
+const struct type *
+type_store_lookup_tagged(struct type_store *store, struct location loc,
+ struct type_tagged_union *tags)
+{
+ const struct type *type = lookup_tagged(store, tags);
+ if (!enforce_tagged_invariants(store, loc, type)) {
+ return &builtin_type_void;
+ }
+ return type_store_lookup_type(store, type);
}
const struct type *
@@ -1008,7 +1050,6 @@ type_store_tagged_to_union(struct type_store *store, const struct type *tagged)
if (tu->type->size == 0) {
continue;
}
- assert(tu->type->size != SIZE_UNDEFINED);
if (tu->type->size > type.size) {
type.size = tu->type->size;
@@ -1085,7 +1126,8 @@ type_store_lookup_enum(struct type_store *store, const struct ast_type *atype,
// - If the resulting union only has one type, return that type
// - Otherwise, return a tagged union of all the selected types
const struct type *
-type_store_reduce_result(struct type_store *store, struct type_tagged_union *in)
+type_store_reduce_result(struct type_store *store, struct location loc,
+ struct type_tagged_union *in)
{
if (!in) {
return &builtin_type_void;
@@ -1093,7 +1135,7 @@ type_store_reduce_result(struct type_store *store, struct type_tagged_union *in)
return in->type;
}
- const struct type *type = type_store_lookup_tagged(store, in);
+ const struct type *type = lookup_tagged(store, in);
struct type_tagged_union _in = type->tagged;
in = &_in;
@@ -1179,5 +1221,5 @@ type_store_reduce_result(struct type_store *store, struct type_tagged_union *in)
if (in->next == NULL) {
return in->type;
}
- return type_store_lookup_tagged(store, in);
+ return type_store_lookup_tagged(store, loc, in);
}
diff --git a/tests/13-tagged.ha b/tests/13-tagged.ha
@@ -221,6 +221,20 @@ fn reject() void = {
let b = a as (u8 | u16);
};"
) != 0);
+
+ // cannot have members of undefined size
+ assert(rt::compile(
+ "fn test() (void | [*]int) = {
+ void;
+ };"
+ ) != 0);
+
+ // cannot have <2 members
+ assert(rt::compile(
+ "fn test() (void | void) = {
+ void;
+ };"
+ ) != 0);
};
export fn main() void = {