~sircmpwn/hare-dev

harec: unify type validity checks for tagged unions v2 PROPOSED

Bor Grošelj Simić: 3
 unify type validity checks for tagged unions
 type_store: make type validity checks consistent
 tests: detect signal-induced exit

 28 files changed, 478 insertions(+), 319 deletions(-)
#787344 alpine.yml success
#787345 freebsd.yml success
harec/patches: SUCCESS in 43s

[unify type validity checks for tagged unions][0] v2 from [Bor Grošelj Simić][1]

[0]: https://lists.sr.ht/~sircmpwn/hare-dev/patches/33267
[1]: mailto:bgs@turminal.net

✓ #787344 SUCCESS harec/patches/alpine.yml  https://builds.sr.ht/~sircmpwn/job/787344
✓ #787345 SUCCESS harec/patches/freebsd.yml https://builds.sr.ht/~sircmpwn/job/787345
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/hare-dev/patches/33267/mbox | git am -3
Learn more about email & git

[PATCH harec v2 1/3] unify type validity checks for tagged unions Export this patch

Signed-off-by: Bor Grošelj Simić <bgs@turminal.net>
---
 include/type_store.h |  4 +--
 src/check.c          | 14 +++++----
 src/type_store.c     | 71 ++++++++++++++++++++++++++++++++++----------
 tests/13-tagged.ha   | 14 +++++++++
 4 files changed, 79 insertions(+), 24 deletions(-)

diff --git a/include/type_store.h b/include/type_store.h
index 4c53d77..8b095ea 100644
--- 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
index 6142334..76e7666 100644
--- 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
index 0de993c..410c464 100644
--- 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 member");
		return false;
	}
	return true;
}

static size_t
sum_tagged_memb(struct type_store *store,
		const struct type_tagged_union *u)
@@ -428,7 +455,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 +495,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 +506,8 @@ 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);
	enforce_tagged_invariants(store, atype->loc, type);
}

static struct dimensions
@@ -512,6 +534,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 tuple member");
			return (struct dimensions){0};
		}
		if (dim.size < memb.size) {
			dim.size = memb.size;
		}
@@ -978,9 +1005,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 +1019,18 @@ 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);
	enforce_tagged_invariants(store, loc, type);
	return type_store_lookup_type(store, type);
}

const struct type *
@@ -1008,7 +1047,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 +1123,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 +1132,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 +1218,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
index 2947e5d..6f0a3c9 100644
--- 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 = {
-- 
2.36.1
Needs rebase

[PATCH harec v2 2/3] type_store: make type validity checks consistent Export this patch

Before this change we didn't properly check for presence of null type in
some aggregate types and types of undefined or zero size weren't
reported in places where they should be.

Signed-off-by: Bor Grošelj Simić <bgs@turminal.net>
---
 include/type_store.h |   9 +--
 src/check.c          |  31 +++++----
 src/eval.c           |   2 +-
 src/type_store.c     | 150 +++++++++++++++++++++++++++++++------------
 tests/03-pointers.ha |  11 ++++
 tests/06-structs.ha  |   4 ++
 6 files changed, 151 insertions(+), 56 deletions(-)

diff --git a/include/type_store.h b/include/type_store.h
index 8b095ea..65030a8 100644
--- a/include/type_store.h
+++ b/include/type_store.h
@@ -37,13 +37,14 @@ const struct type *type_store_lookup_with_flags(struct type_store *store,
	const struct type *type, unsigned int flags);

const struct type *type_store_lookup_pointer(struct type_store *store,
	const struct type *referent, unsigned int ptrflags);
	struct location loc, const struct type *referent, unsigned int ptrflags);

const struct type *type_store_lookup_array(struct type_store *store,
	const struct type *members, size_t len, bool expandable);
	struct location loc, const struct type *members, size_t len,
	bool expandable);

const struct type *type_store_lookup_slice(struct type_store *store,
	const struct type *members);
	struct location loc, const struct type *members);

const struct type *type_store_lookup_alias(struct type_store *store,
	const struct type *secondary);
@@ -56,7 +57,7 @@ const struct type *type_store_tagged_to_union(
	struct type_store *store, const struct type *tagged);

const struct type *type_store_lookup_tuple(struct type_store *store,
	struct type_tuple *values, struct location loc);
	 struct location loc, struct type_tuple *values);

const struct type *type_store_lookup_enum(struct type_store *store,
	const struct ast_type *atype, bool exported);
diff --git a/src/check.c b/src/check.c
index 76e7666..2d70e67 100644
--- a/src/check.c
+++ b/src/check.c
@@ -333,7 +333,8 @@ check_expr_alloc_init(struct context *ctx,
		assert(htype->array.members == atype->array.members);
		objtype = hint;
	}
	expr->result = type_store_lookup_pointer(ctx->store, objtype, ptrflags);
	expr->result = type_store_lookup_pointer(ctx->store, aexpr->loc,
			objtype, ptrflags);
	if (expr->result->size == 0 || expr->result->size == SIZE_UNDEFINED) {
		error(ctx, aexpr->loc, expr,
			"Cannot allocate object of zero or undefined size");
@@ -386,7 +387,8 @@ check_expr_alloc_slice(struct context *ctx,
	}

	const struct type *membtype = type_dealias(objtype)->array.members;
	expr->result = type_store_lookup_slice(ctx->store, membtype);
	expr->result = type_store_lookup_slice(ctx->store,
		aexpr->alloc.init->loc, membtype);

	if (objtype->storage == STORAGE_ARRAY
			&& objtype->array.expandable) {
@@ -424,7 +426,8 @@ check_expr_alloc_copy(struct context *ctx,

	check_expression(ctx, aexpr->alloc.init, expr->alloc.init, hint);
	result = type_dealias(expr->alloc.init->result);
	expr->result = type_store_lookup_slice(ctx->store, result->array.members);
	expr->result = type_store_lookup_slice(ctx->store,
			aexpr->alloc.init->loc, result->array.members);
}

static void
@@ -1258,8 +1261,8 @@ lower_vaargs(struct context *ctx,
	}

	// XXX: This error handling is minimum-effort and bad
	const struct type *hint = type_store_lookup_array(
		ctx->store, type, SIZE_UNDEFINED, false);
	const struct type *hint = type_store_lookup_array(ctx->store,
			val.loc, type, SIZE_UNDEFINED, false);
	check_expression(ctx, &val, vaargs, hint);
	if (vaargs->result->storage != STORAGE_ARRAY
			|| vaargs->result->array.members != type) {
@@ -1561,7 +1564,8 @@ check_expr_array(struct context *ctx,
		error(ctx, aexpr->loc, expr, "Cannot infer array type from context, try casting it to the desired type");
		return;
	}
	expr->result = type_store_lookup_array(ctx->store, type, len, expand);
	expr->result = type_store_lookup_array(ctx->store, aexpr->loc,
			type, len, expand);
}

static void
@@ -2457,7 +2461,7 @@ check_expr_slice(struct context *ctx,
	if (dtype->storage == STORAGE_SLICE) {
		expr->result = atype;
	} else {
		expr->result = type_store_lookup_slice(ctx->store,
		expr->result = type_store_lookup_slice(ctx->store, aexpr->loc,
			dtype->array.members);
	}
}
@@ -2842,8 +2846,12 @@ check_expr_tuple(struct context *ctx,
			return;
		}
	} else {
		expr->result = type_store_lookup_tuple(ctx->store, &result,
				aexpr->loc);
		expr->result = type_store_lookup_tuple(ctx->store,
				aexpr->loc, &result);
		if (expr->result == &builtin_type_void) {
			// an error occured
			return;
		}
	}

	ttuple = &type_dealias(expr->result)->tuple;
@@ -2924,7 +2932,7 @@ check_expr_unarithm(struct context *ctx,
		break;
	case UN_ADDRESS:
		expr->result = type_store_lookup_pointer(
			ctx->store, operand->result, 0);
			ctx->store, aexpr->loc, operand->result, 0);
		break;
	case UN_DEREF:
		if (type_dealias(operand->result)->storage != STORAGE_POINTER) {
@@ -3183,7 +3191,8 @@ check_function(struct context *ctx,
				ctx->store, params->type);
		if (fntype->func.variadism == VARIADISM_HARE
				&& !params->next) {
			type = type_store_lookup_slice(ctx->store, type);
			type = type_store_lookup_slice(ctx->store,
				params->loc, type);
		}
		scope_insert(decl->func.scope, O_BIND,
			&ident, &ident, type, NULL);
diff --git a/src/eval.c b/src/eval.c
index 89567e9..0ab27ed 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -808,7 +808,7 @@ eval_unarithm(struct context *ctx, struct expression *in, struct expression *out
			return EVAL_INVALID;
		}
		out->result = type_store_lookup_pointer(
			ctx->store, in->unarithm.operand->result, 0);
			ctx->store, in->loc, in->unarithm.operand->result, 0);
		out->constant.object = in->unarithm.operand->access.object;
		out->constant.ival = 0;
		return EVAL_OK;
diff --git a/src/type_store.c b/src/type_store.c
index 410c464..0255cdc 100644
--- a/src/type_store.c
+++ b/src/type_store.c
@@ -147,7 +147,8 @@ builtin_for_type(const struct type *type)
static struct struct_field *
struct_insert_field(struct type_store *store, struct struct_field **fields,
	enum type_storage storage, size_t *size, size_t *usize, size_t *align,
	const struct ast_struct_union_type *atype, bool *ccompat, bool size_only)
	const struct ast_struct_union_type *atype, bool *ccompat, bool size_only,
	bool last)
{
	while (*fields && (!atype->name || !(*fields)->name || strcmp((*fields)->name, atype->name) < 0)) {
		fields = &(*fields)->next;
@@ -174,13 +175,16 @@ struct_insert_field(struct type_store *store, struct struct_field **fields,
	}
	if (dim.size == 0) {
		error(store->check_context, atype->type->loc,
			"Struct field size cannot be zero");
			"Type of size 0 is not a valid struct/union member");
		return NULL;
	} else if (dim.align == 0) {
	}
	if (!last && dim.size == SIZE_UNDEFINED) {
		error(store->check_context, atype->type->loc,
			"Struct field alignment cannot be zero");
			"Type of undefined size is not a valid struct/union member");
		return NULL;
	}
	assert(dim.align != ALIGN_UNDEFINED);
	assert(dim.align != 0);

	if (atype->offset) {
		*ccompat = false;
@@ -284,7 +288,8 @@ struct_init_from_atype(struct type_store *store, enum type_storage storage,
	assert(storage == STORAGE_STRUCT || storage == STORAGE_UNION);
	while (atype) {
		struct struct_field *field = struct_insert_field(store, fields,
			storage, size, &usize, align, atype, ccompat, size_only);
			storage, size, &usize, align, atype, ccompat, size_only,
			atype->next == NULL);
		if (field == NULL) {
			return;
		}
@@ -576,31 +581,35 @@ tuple_init_from_atype(struct type_store *store,
	struct dimensions dim = {0};
	while (atuple) {
		struct dimensions memb = {0};
		size_t offset = 0;
		if (type) {
			memb = lookup_atype_with_dimensions(store, &cur->type, atuple->type);
			if (memb.size == 0 || memb.align == 0) {
				error(store->check_context, atuple->type->loc,
					"Tuple member types must have nonzero size and alignment");
				return dim;
			}
			cur->offset = dim.size % memb.align + dim.size;
		} else {
			memb = lookup_atype_with_dimensions(store, NULL, atuple->type);
			if (memb.size == 0 || memb.align == 0) {
				error(store->check_context, atuple->type->loc,
					"Tuple member types must have nonzero size and alignment");
				return dim;
			}
		}
		if (memb.size == 0) {
			error(store->check_context, atype->loc,
				"Type of size 0 is not a valid tuple member");
			return (struct dimensions){0};
		}
		if (memb.size == SIZE_UNDEFINED) {
			error(store->check_context, atype->loc,
				"Type of undefined size is not a valid tuple member");
			return (struct dimensions){0};
		}
		offset = dim.size % memb.align + dim.size;
		dim.size += dim.size % memb.align + memb.size;
		if (dim.align < memb.align) {
			dim.align = memb.align;
		}

		atuple = atuple->next;
		if (atuple && type) {
			cur->next = xcalloc(1, sizeof(struct type_tuple));
			cur = cur->next;
		if (type) {
			cur->offset = offset;
			if (atuple) {
				cur->next = xcalloc(1, sizeof(struct type_tuple));
				cur = cur->next;
			}
		}
	}
	if (type) {
@@ -636,6 +645,7 @@ type_init_from_atype(struct type_store *store,
	case STORAGE_ICONST:
	case STORAGE_RCONST:
	case STORAGE_ENUM:
	case STORAGE_NULL:
		assert(0); // Invariant
	case STORAGE_BOOL:
	case STORAGE_CHAR:
@@ -718,9 +728,17 @@ type_init_from_atype(struct type_store *store,
			memb = lookup_atype_with_dimensions(store,
				&type->array.members, atype->array.members);
		}
		if (memb.size == SIZE_UNDEFINED) {
		// XXX: I'm not sure these checks are *exactly* right, we might
		// still be letting some invalid stuff through
		if (type->array.length != SIZE_UNDEFINED && memb.size == 0) {
			error(store->check_context, atype->loc,
				"Array member must have defined size");
				"Type of size 0 is not a valid array member");
			*type = builtin_type_void;
			return (struct dimensions){0};
		}
		if (type->array.length != SIZE_UNDEFINED && memb.size == SIZE_UNDEFINED) {
			error(store->check_context, atype->loc,
				"Type of undefined size is not a valid array member");
			*type = builtin_type_void;
			return (struct dimensions){0};
		}
@@ -750,7 +768,7 @@ type_init_from_atype(struct type_store *store,
			if (atype->func.variadism == VARIADISM_HARE
					&& !aparam->next) {
				param->type = type_store_lookup_slice(
					store, param->type);
					store, aparam->loc, param->type);
			}
			next = &param->next;
		}
@@ -813,11 +831,6 @@ type_init_from_atype(struct type_store *store,
			tuple_init_from_atype(store, type, atype);
		}
		break;
	case STORAGE_NULL:
		error(store->check_context, atype->loc,
			"Type null used in invalid context");
		*type = builtin_type_void;
		return (struct dimensions){0};
	}
	return (struct dimensions){ .size = type->size, .align = type->align };
}
@@ -930,10 +943,15 @@ type_store_lookup_with_flags(struct type_store *store,
}

const struct type *
type_store_lookup_pointer(struct type_store *store,
type_store_lookup_pointer(struct type_store *store, struct location loc,
	const struct type *referent, unsigned int ptrflags)
{
	if (referent->storage == STORAGE_NULL) {
		error(store->check_context, loc,
			"Null type not allowed in this context");
	}
	referent = lower_const(referent, NULL);

	struct type ptr = {
		.storage = STORAGE_POINTER,
		.pointer = {
@@ -947,10 +965,30 @@ type_store_lookup_pointer(struct type_store *store,
}

const struct type *
type_store_lookup_array(struct type_store *store,
type_store_lookup_array(struct type_store *store, struct location loc,
	const struct type *members, size_t len, bool expandable)
{
	if (members->storage == STORAGE_NULL) {
		error(store->check_context, loc,
			"Null type not allowed in this context");
		return &builtin_type_void;
	}
	members = lower_const(members, NULL);
	// XXX: I'm not sure these checks are *exactly* right, we might still
	// be letting some invalid stuff pass
	if (len != SIZE_UNDEFINED && members->size == 0) {
		error(store->check_context, loc,
			"Type of size 0 is not a valid array member");
		return &builtin_type_void;
	}
	if (len != SIZE_UNDEFINED && members->size == SIZE_UNDEFINED) {
		error(store->check_context, loc,
			"Type of undefined size is not a valid member of a bounded array");
		return &builtin_type_void;
	}
	assert(members->align != 0);
	assert(members->align != ALIGN_UNDEFINED);

	struct type array = {
		.storage = STORAGE_ARRAY,
		.array = {
@@ -967,9 +1005,28 @@ type_store_lookup_array(struct type_store *store,
}

const struct type *
type_store_lookup_slice(struct type_store *store, const struct type *members)
type_store_lookup_slice(struct type_store *store, struct location loc,
	const struct type *members)
{
	if (members->storage == STORAGE_NULL) {
		error(store->check_context, loc,
			"Null type not allowed in this context");
		return &builtin_type_void;
	}
	members = lower_const(members, NULL);
	if (members->size == 0) {
		error(store->check_context, loc,
			"Type of size 0 is not a valid slice member");
		return &builtin_type_void;
	}
	if (members->size == SIZE_UNDEFINED) {
		error(store->check_context, loc,
			"Type of undefined size is not a valid slice member");
		return &builtin_type_void;
	}
	assert(members->align != 0);
	assert(members->align != ALIGN_UNDEFINED);

	struct type slice = {
		.storage = STORAGE_SLICE,
		.array = {
@@ -1067,21 +1124,34 @@ type_store_tagged_to_union(struct type_store *store, const struct type *tagged)
}

const struct type *
type_store_lookup_tuple(struct type_store *store, struct type_tuple *values,
		struct location loc)
type_store_lookup_tuple(struct type_store *store, struct location loc,
		struct type_tuple *values)
{
	struct type type = {
		.storage = STORAGE_TUPLE,
	};
	for (struct type_tuple *t = values; t; t = t->next) {
		if (t->type->storage == STORAGE_NULL) {
			error(store->check_context, loc,
				"Null type not allowed in this context");
			return &builtin_type_void;
		}
		t->type = lower_const(t->type, NULL);
		if (t->type->align > type.align) {
			type.align = t->type->align;
		if (t->type->size == 0) {
			error(store->check_context, loc,
				"Type of size 0 is not a valid tuple member");
			return &builtin_type_void;
		}
		if (t->type->size == 0 || t->type->align == 0) {
		if (t->type->size == SIZE_UNDEFINED) {
			error(store->check_context, loc,
				"Tuple values must have nonzero size and alignment");
			break;
				"Type of undefined size is not a valid tuple member");
			return &builtin_type_void;
		}
		assert(t->type->align != 0);
		assert(t->type->align != ALIGN_UNDEFINED);

		if (t->type->align > type.align) {
			type.align = t->type->align;
		}
		t->offset = type.size % t->type->align + type.size;
		type.size += type.size % t->type->align + t->type->size;
@@ -1178,9 +1248,9 @@ type_store_reduce_result(struct type_store *store, struct location loc,
			}
			if ((it->pointer.flags & PTR_NULLABLE)
					|| (jt->pointer.flags & PTR_NULLABLE)) {
				it = type_store_lookup_pointer(store,
				it = type_store_lookup_pointer(store, loc,
					it->pointer.referent, PTR_NULLABLE);
				jt = type_store_lookup_pointer(store,
				jt = type_store_lookup_pointer(store, loc,
					jt->pointer.referent, PTR_NULLABLE);
				if (it == jt) {
					dropped = true;
@@ -1211,7 +1281,7 @@ type_store_reduce_result(struct type_store *store, struct location loc,

	if (null != NULL && ptr != NULL) {
		*null = (*null)->next;
		ptr->type = type_store_lookup_pointer(store,
		ptr->type = type_store_lookup_pointer(store, loc,
			ptr->type->pointer.referent, PTR_NULLABLE);
	}

diff --git a/tests/03-pointers.ha b/tests/03-pointers.ha
index 1bba06d..6ce5a68 100644
--- a/tests/03-pointers.ha
+++ b/tests/03-pointers.ha
@@ -54,6 +54,17 @@ fn reject() void = {
			void;
		};
	") != 0);
	assert(rt::compile("
		type s = *null;
		fn test() void = {
			void;
		};
	") != 0);
	assert(rt::compile("
		fn test() void = {
			let a = &null;
		};
	") != 0);
	assert(rt::compile("
		fn test() void = {
			let a = &3: null;
diff --git a/tests/06-structs.ha b/tests/06-structs.ha
index a6b829f..3813ac9 100644
--- a/tests/06-structs.ha
+++ b/tests/06-structs.ha
@@ -177,6 +177,10 @@ fn invariants() void = {
	"fn test() void = { let x: struct { y: int } = struct { y: int = 10 }; x.z; };",
	// Untyped field for unnamed struct:
	"fn test() void = { let x = struct { x = 10 }; };",
	// Members of undefined size before last
	"type s = struct { x: [*]int, a: str };",
	// Members of size 0
	"type s = struct { x: void, a: str };"
	];
	for (let i = 0z; i < len(failures); i += 1) {
		assert(rt::compile(failures[i]) != 0);
-- 
2.36.1

[PATCH harec v2 3/3] tests: detect signal-induced exit Export this patch

Previous checks for rt::compile return value succeeded in cases harec
exited with SIGABRT or SIGSEGV and that resulted in masked bugs on
multiple occasions.

Signed-off-by: Bor Grošelj Simić <bgs@turminal.net>
---
v2 -> v3: use a proper method of distinguishing exit-by-signal
 rt/compile.ha              |  9 +++--
 tests/00-constants.ha      | 76 +++++++++++++++++++-------------------
 tests/01-arrays.ha         | 10 ++---
 tests/03-pointers.ha       | 50 ++++++++++++-------------
 tests/05-implicit-casts.ha | 42 ++++++++++-----------
 tests/06-structs.ha        |  4 +-
 tests/08-slices.ha         | 24 ++++++------
 tests/11-globals.ha        |  6 +--
 tests/12-loops.ha          | 12 +++---
 tests/13-tagged.ha         | 34 ++++++++---------
 tests/15-enums.ha          | 20 +++++-----
 tests/19-append.ha         | 26 ++++++-------
 tests/21-tuples.ha         | 26 ++++++-------
 tests/23-errors.ha         | 50 ++++++++++++++-----------
 tests/24-imports.ha        | 54 +++++++++++++--------------
 tests/27-rt.ha             |  4 +-
 tests/28-insert.ha         | 18 ++++-----
 tests/34-declarations.ha   | 22 +++++------
 18 files changed, 248 insertions(+), 239 deletions(-)

diff --git a/rt/compile.ha b/rt/compile.ha
index 7be8837..d6e1972 100644
--- a/rt/compile.ha
+++ b/rt/compile.ha
@@ -1,5 +1,8 @@
export type exited = int, signaled = int;
export type exit_status = (exited | signaled);

// Runs the Hare compiler and returns the exit status.
export fn compile(src: const str) int = {
export fn compile(src: const str) exit_status = {
	let status = 0;
	let pipefd = [-1, -1];
	assert(pipe2(&pipefd, 0) == 0);
@@ -38,7 +41,7 @@ export fn compile(src: const str) int = {
		wait4(child, &status, 0, null);
	};

	return if (wifexited(status)) wexitstatus(status)
		else if (wifsignaled(status)) wtermsig(status)
	return if (wifexited(status)) wexitstatus(status): exited
		else if (wifsignaled(status)) wtermsig(status): signaled
		else abort();
};
diff --git a/tests/00-constants.ha b/tests/00-constants.ha
index 27d8e7c..73b5de3 100644
--- a/tests/00-constants.ha
+++ b/tests/00-constants.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

type my_enum = enum u8 {
	FOO,
@@ -34,61 +34,61 @@ fn assignment() void = {
	let rtu3: (rune | u64 | void) = void;

	i = 127;
	assert(rt::compile("export fn main() void = { let i = 0i8; i = 128; };") != 0);
	assert(compile("export fn main() void = { let i = 0i8; i = 128; };") as exited != EXIT_SUCCESS);
	u = 18446744073709551615;
	assert(rt::compile("export fn main() void = { let u = 0u32; u = 4294967296; };") != 0);
	assert(rt::compile("export fn main() void = { let f = 0.0f64; f = 0; };") != 0);
	assert(rt::compile("export fn main() void = { let r = 'a'; r = 0; };") != 0);
	assert(compile("export fn main() void = { let u = 0u32; u = 4294967296; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let f = 0.0f64; f = 0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let r = 'a'; r = 0; };") as exited != EXIT_SUCCESS);
	e = 0;
	assert(rt::compile("type my_enum = enum u8 { FOO }; export fn main() void = { let e: my_enum = my_enum::FOO; e = 256; };") != 0);
	assert(rt::compile("export fn main() void = { let p: nullable *void = null; p = 0; };") != 0);
	assert(rt::compile("export fn main() void = { let b = false; b = 0; };") != 0);
	assert(rt::compile("export fn main() void = { let n = null; n = 0; };") != 0);
	assert(rt::compile("export fn main() void = { let s: struct { i: int } = struct { i: int = 0 }; s = 0; };") != 0);
	assert(rt::compile("export fn main() void = { let t = (0, 1); t = 0; };") != 0);
	assert(rt::compile("export fn main() void = { let a = [0, 1]; a = 0; };") != 0);
	assert(rt::compile("export fn main() void = { let s = \"\"; s = 0; };") != 0);
	assert(compile("type my_enum = enum u8 { FOO }; export fn main() void = { let e: my_enum = my_enum::FOO; e = 256; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let p: nullable *void = null; p = 0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let b = false; b = 0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let n = null; n = 0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let s: struct { i: int } = struct { i: int = 0 }; s = 0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let t = (0, 1); t = 0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let a = [0, 1]; a = 0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let s = \"\"; s = 0; };") as exited != EXIT_SUCCESS);
	itu1 = 0;
	itu2 = 0;
	itu3 = 0;
	assert(rt::compile("export fn main() void = { let itu4: (u32 | u64 | void) = void; itu4 = 0; };") != 0);
	assert(rt::compile("export fn main() void = { let itu5: (str | void) = void; itu5 = 0; };") != 0);
	assert(compile("export fn main() void = { let itu4: (u32 | u64 | void) = void; itu4 = 0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let itu5: (str | void) = void; itu5 = 0; };") as exited != EXIT_SUCCESS);

	assert(rt::compile("export fn main() void = { let i = 0i8; i = 0.0; };") != 0);
	assert(rt::compile("export fn main() void = { let u = 0u8; u = 0.0; };") != 0);
	assert(compile("export fn main() void = { let i = 0i8; i = 0.0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let u = 0u8; u = 0.0; };") as exited != EXIT_SUCCESS);
	f = 0.0;
	assert(rt::compile("export fn main() void = { let r = 'a'; r = 0.0; };") != 0);
	assert(rt::compile("type my_enum = enum u8 { FOO }; export fn main() void = { let e: my_enum = my_enum::FOO; e = 0.0; };") != 0);
	assert(rt::compile("export fn main() void = { let p: nullable *void = null; p = 0.0; };") != 0);
	assert(rt::compile("export fn main() void = { let b = false; b = 0.0; };") != 0);
	assert(rt::compile("export fn main() void = { let n = null; n = 0.0; };") != 0);
	assert(rt::compile("export fn main() void = { let s: struct { i: int } = struct { i: int = 0 }; s = 0.0; };") != 0);
	assert(rt::compile("export fn main() void = { let t = (0, 1); t = 0.0; };") != 0);
	assert(rt::compile("export fn main() void = { let a = [0, 1]; a = 0.0; };") != 0);
	assert(rt::compile("export fn main() void = { let s = ""; s = 0.0; };") != 0);
	assert(compile("export fn main() void = { let r = 'a'; r = 0.0; };") as exited != EXIT_SUCCESS);
	assert(compile("type my_enum = enum u8 { FOO }; export fn main() void = { let e: my_enum = my_enum::FOO; e = 0.0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let p: nullable *void = null; p = 0.0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let b = false; b = 0.0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let n = null; n = 0.0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let s: struct { i: int } = struct { i: int = 0 }; s = 0.0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let t = (0, 1); t = 0.0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let a = [0, 1]; a = 0.0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let s = ""; s = 0.0; };") as exited != EXIT_SUCCESS);
	ftu1 = 0.0;
	ftu2 = 0.0;
	ftu3 = 0.0;
	assert(rt::compile("type my_f32 = f32; export fn main() void = { let ftu4: (f32 | my_f32 | void) = void; ftu4 = 0.0; };") != 0);
	assert(rt::compile("export fn main() void = { let ftu5: (str | void) = void; ftu5 = 0.0; };") != 0);
	assert(compile("type my_f32 = f32; export fn main() void = { let ftu4: (f32 | my_f32 | void) = void; ftu4 = 0.0; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let ftu5: (str | void) = void; ftu5 = 0.0; };") as exited != EXIT_SUCCESS);

	i = 'a';
	u = 'a';
	assert(rt::compile("export fn main() void = { let f = 0.0f64; f = 'a'; };") != 0);
	assert(compile("export fn main() void = { let f = 0.0f64; f = 'a'; };") as exited != EXIT_SUCCESS);
	r = 'a';
	e = 'a';
	assert(rt::compile("export fn main() void = { let p: nullable *void = null; p = 'a'; };") != 0);
	assert(rt::compile("export fn main() void = { let b = false; b = 'a'; };") != 0);
	assert(rt::compile("export fn main() void = { let n = null; n = 'a'; };") != 0);
	assert(rt::compile("export fn main() void = { let s: struct { i: int } = struct { i: int = 0 }; s = 'a'; };") != 0);
	assert(rt::compile("export fn main() void = { let t = (0, 1); t = 'a'; };") != 0);
	assert(rt::compile("export fn main() void = { let a = [0, 1]; a = 'a'; };") != 0);
	assert(rt::compile("export fn main() void = { let s = ""; s = 'a'; };") != 0);
	assert(compile("export fn main() void = { let p: nullable *void = null; p = 'a'; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let b = false; b = 'a'; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let n = null; n = 'a'; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let s: struct { i: int } = struct { i: int = 0 }; s = 'a'; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let t = (0, 1); t = 'a'; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let a = [0, 1]; a = 'a'; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let s = ""; s = 'a'; };") as exited != EXIT_SUCCESS);
	rtu1 = 'a';
	rtu2 = 'a';
	rtu3 = 'a';
	assert(rt::compile("export fn main() void = { let rtu4: (u32 | u64 | void) = void; rtu4 = 'a'; };") != 0);
	assert(rt::compile("export fn main() void = { let rtu5: (str | void) = void; rtu5 = 'a'; };") != 0);
	assert(compile("export fn main() void = { let rtu4: (u32 | u64 | void) = void; rtu4 = 'a'; };") as exited != EXIT_SUCCESS);
	assert(compile("export fn main() void = { let rtu5: (str | void) = void; rtu5 = 'a'; };") as exited != EXIT_SUCCESS);
};

fn aggregates() void = {
diff --git a/tests/01-arrays.ha b/tests/01-arrays.ha
index ea9b845..31da905 100644
--- a/tests/01-arrays.ha
+++ b/tests/01-arrays.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

fn indexing() void = {
	let x = [1, 2, 3];
@@ -50,7 +50,7 @@ fn assignment() void = {
	assert(y[0] == 1 && y[1] == 2 && y[2] == 3);
	assert(z[0] == 1 && z[1] == 2 && z[2] == 3);

	assert(rt::compile("
	assert(compile("
		export fn main() void = {
			let a: [3]uint = [1u,2u,3u];
			let b: uint = 0;
@@ -58,15 +58,15 @@ fn assignment() void = {
			let ptr: *[3]uint = &a;
			ptr = &b;
		};
	") != 0);
	") as exited != EXIT_SUCCESS);

	assert(rt::compile(`
	assert(compile(`
		export fn main() void = {
			let a: *[*]uint = &[1u,2u,3u];
			let b: [3]str = ["a", "b", "c"];
			a = &b;
		};
	`) != 0);
	`) as exited != EXIT_SUCCESS);
};

fn param(x: [3]int) void = {
diff --git a/tests/03-pointers.ha b/tests/03-pointers.ha
index 6ce5a68..532dbf8 100644
--- a/tests/03-pointers.ha
+++ b/tests/03-pointers.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

type intp = *int;

@@ -17,9 +17,9 @@ fn _nullable() void = {
	x = &y;
	assert(*(x: *int) == 42);

	assert(rt::compile(
	assert(compile(
		"fn test() void = { let x: nullable *int = null; let z = *x; };",
	) != 0);
	) as exited != EXIT_SUCCESS);
};

fn casts() void = {
@@ -48,67 +48,67 @@ fn casts() void = {
};

fn reject() void = {
	assert(rt::compile("
	assert(compile("
		type s = null;
		fn test() void = {
			void;
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		type s = *null;
		fn test() void = {
			void;
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		fn test() void = {
			let a = &null;
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		fn test() void = {
			let a = &3: null;
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		fn test() void = {
			let b: nullable *int = null;
			let a = b as null;
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		fn test() void = {
			let a = (null, 3);
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		fn test() void = {
			let a: []null = [null];
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		fn test() void = {
			let a = [null];
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		fn test() void = {
			let a: [_]null = [null];
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		fn test() void = {
			let a = null;
		};
	") != 0);
	") as exited != EXIT_SUCCESS);

	// type assertions on non-nullable pointers are prohibited
	assert(rt::compile("
	assert(compile("
		fn test() void = {
			let a: *int = &4;
			assert(a as *int);
		};
	") != 0);
	") as exited != EXIT_SUCCESS);
};

export fn main() void = {
diff --git a/tests/05-implicit-casts.ha b/tests/05-implicit-casts.ha
index d50a25d..e633f29 100644
--- a/tests/05-implicit-casts.ha
+++ b/tests/05-implicit-casts.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

type subtype = struct {
	foo: int,
@@ -44,7 +44,7 @@ fn rules() void = {

	// Implementation-defined precision
	if (size(int) == 8) {
		assert(rt::compile("fn test() void = { let i: int = 42i64; };") == 0);
		assert(compile("fn test() void = { let i: int = 42i64; };") as exited == EXIT_SUCCESS);
	};
	let i: int = 42i;
	i = 42i32;
@@ -52,7 +52,7 @@ fn rules() void = {
	i = 42i8;

	if (size(uint) == 8) {
		assert(rt::compile("fn test() void = { let u: uint = 42u64; };") == 0);
		assert(compile("fn test() void = { let u: uint = 42u64; };") as exited == EXIT_SUCCESS);
	};
	let u: uint = 42u;
	u = 42u32;
@@ -60,20 +60,20 @@ fn rules() void = {
	u = 42u8;

	// Precision loss (should fail)
	assert(rt::compile("fn test() void = { let _i8: i8 = 42i16; };") != 0);
	assert(rt::compile("fn test() void = { let _i8: i8 = 42i32; };") != 0);
	assert(rt::compile("fn test() void = { let _i8: i8 = 42i64; };") != 0);
	assert(rt::compile("fn test() void = { let _i8: i8 = 42i; };") != 0);
	assert(rt::compile("fn test() void = { let _i16: i16 = 42i32; };") != 0);
	assert(rt::compile("fn test() void = { let _i16: i16 = 42i64; };") != 0);
	assert(rt::compile("fn test() void = { let _i32: i32 = 42i64; };") != 0);
	assert(rt::compile("fn test() void = { let _u8: u8 = 42u16; };") != 0);
	assert(rt::compile("fn test() void = { let _u8: u8 = 42u32; };") != 0);
	assert(rt::compile("fn test() void = { let _u8: u8 = 42u64; };") != 0);
	assert(rt::compile("fn test() void = { let _u8: u8 = 42u; };") != 0);
	assert(rt::compile("fn test() void = { let _u16: u16 = 42u32; };") != 0);
	assert(rt::compile("fn test() void = { let _u16: u16 = 42u64; };") != 0);
	assert(rt::compile("fn test() void = { let _u32: u32 = 42u64; };") != 0);
	assert(compile("fn test() void = { let _i8: i8 = 42i16; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _i8: i8 = 42i32; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _i8: i8 = 42i64; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _i8: i8 = 42i; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _i16: i16 = 42i32; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _i16: i16 = 42i64; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _i32: i32 = 42i64; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _u8: u8 = 42u16; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _u8: u8 = 42u32; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _u8: u8 = 42u64; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _u8: u8 = 42u; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _u16: u16 = 42u32; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _u16: u16 = 42u64; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { let _u32: u32 = 42u64; };") as exited != EXIT_SUCCESS);

	// Pointer conversions
	let cchr: *const char = "hello world";
@@ -87,12 +87,12 @@ fn rules() void = {
	let sptr: *struct { foo: int } = &super3 { ... };

	// Invalid pointer conversions
	assert(rt::compile(
	assert(compile(
		"fn test() void = { let x: nullable *int = null; let y: *int = x; };"
	) != 0);
	assert(rt::compile(
	) as exited != EXIT_SUCCESS);
	assert(compile(
		"fn test() void = { let x: int = 10; let y: *int = &x; let y: *uint = x; };"
	) != 0);
	) as exited != EXIT_SUCCESS);

	// Non-const from const (copy)
	const j = 10;
diff --git a/tests/06-structs.ha b/tests/06-structs.ha
index 3813ac9..5e03f8a 100644
--- a/tests/06-structs.ha
+++ b/tests/06-structs.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

fn padding() void = {
	assert(size(struct { x: i32, y: i32 }) == 8);
@@ -183,7 +183,7 @@ fn invariants() void = {
	"type s = struct { x: void, a: str };"
	];
	for (let i = 0z; i < len(failures); i += 1) {
		assert(rt::compile(failures[i]) != 0);
		assert(compile(failures[i]) as exited != EXIT_SUCCESS);
	};
};

diff --git a/tests/08-slices.ha b/tests/08-slices.ha
index b3ec5bc..325517b 100644
--- a/tests/08-slices.ha
+++ b/tests/08-slices.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

fn from_array() void = {
	let src = [1, 2, 3];
@@ -55,12 +55,12 @@ fn measurements() void = {
fn indexing() void = {
	let x = [1, 3, 3, 7];
	assert(x[0] == 1 && x[1] == 3 && x[2] == 3 && x[3] == 7);
	assert(rt::compile(
	assert(compile(
		"fn test() void = { let x: []int = [1, 2, 3]; x[\"hello\"]; };"
	) != 0, "non-numeric index");
	assert(rt::compile(
	) as exited != EXIT_SUCCESS);
	assert(compile(
		"fn test() void = { let x = 10; x[10]; };"
	) != 0, "indexing non-array, non-slice object");
	) as exited != EXIT_SUCCESS);
};

fn zero3(s: []int) void = {
@@ -90,9 +90,9 @@ fn assignment() void = {
	assert(z[0] == 1 && z[1] == 42 && z[2] == 69 && z[3] == 1337 && z[4] == 5);
	z[2..5] = y;
	assert(z[0] == 1 && z[1] == 42 && z[2] == 0 && z[3] == 0 && z[4] == 0);
	assert(rt::compile(
	assert(compile(
		"export fn main() void = { let a: []int = [1]; a[..] += a; };"
	) != 0, "binop slice assignment");
	) as exited != EXIT_SUCCESS);
};

fn assert_slice_eq(actual: []int, expected: []int) void = {
@@ -124,12 +124,12 @@ fn slicing() void = {
	assert_slice_eq(p[1..], [2, 3, 4, 5]);
	assert_slice_eq(p[5..], []);

	assert(rt::compile(
	assert(compile(
		"fn test() void = { let x = \"test\"; x[1..3]; };"
	) != 0, "slicing non-array, non-slice object");
	assert(rt::compile(
	) as exited != EXIT_SUCCESS);
	assert(compile(
		"fn test() void = { let x = [1, 2, 3]; x[\"hi\"..]; };"
	) != 0, "slicing object with non-array, non-slice range");
	) as exited != EXIT_SUCCESS);
};

type tree = struct {
@@ -138,7 +138,7 @@ type tree = struct {
};

fn sum_tree(t: tree) u64 = {
 	let sum = t.value;
	let sum = t.value;
	for (let i = 0z; i < len(t.children); i += 1) {
		sum += sum_tree(t.children[i]);
	};
diff --git a/tests/11-globals.ha b/tests/11-globals.ha
index 15ef8c3..da1f30e 100644
--- a/tests/11-globals.ha
+++ b/tests/11-globals.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

let x: int = 42, y: int = 69;

@@ -58,8 +58,8 @@ fn storage() void = {
};

fn invariants() void = {
	assert(rt::compile("fn test() int; let x: int = test();") != 0);
	assert(rt::compile("const a: u8 = 2; const b: u8 = a + 5;") != 0);
	assert(compile("fn test() int; let x: int = test();") as exited != EXIT_SUCCESS);
	assert(compile("const a: u8 = 2; const b: u8 = a + 5;") as exited != EXIT_SUCCESS);
};

fn counter() int = {
diff --git a/tests/12-loops.ha b/tests/12-loops.ha
index 025f66a..ea30cb1 100644
--- a/tests/12-loops.ha
+++ b/tests/12-loops.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

fn scope() void = {
	let x = 0;
@@ -9,7 +9,7 @@ fn scope() void = {
			break;
		};
	};
	assert(rt::compile("fn test() void = { for (true) { let x = 10; }; x; };") != 0);
	assert(compile("fn test() void = { for (true) { let x = 10; }; x; };") as exited != EXIT_SUCCESS);
	// To make sure that the afterthought is part of the loop's scope
	for (let i = 0; true; (if (true) { break; })) true;
};
@@ -91,10 +91,10 @@ fn label() void = {
		};
	};
	assert(i == 8);
	assert(rt::compile("fn test() void = { :foo for (true) { break :bar; }; };") != 0);
	assert(rt::compile("fn test() void = { for (true) { break :bar; }; };") != 0);
	assert(rt::compile("fn test() void = { break :bar; };") != 0);
	assert(rt::compile("fn test() void = { :foo for (true) { :foo for (true) void; } ; };") != 0);
	assert(compile("fn test() void = { :foo for (true) { break :bar; }; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { for (true) { break :bar; }; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { break :bar; };") as exited != EXIT_SUCCESS);
	assert(compile("fn test() void = { :foo for (true) { :foo for (true) void; } ; };") as exited != EXIT_SUCCESS);
};

type abool = bool;
diff --git a/tests/13-tagged.ha b/tests/13-tagged.ha
index 6f0a3c9..912b540 100644
--- a/tests/13-tagged.ha
+++ b/tests/13-tagged.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

fn measurements() void = {
	const x: (u8 | u16 | u32 | u64) = 1337u16;	// With padding
@@ -56,19 +56,19 @@ fn reduction() void = {
	const b: (i16 | i8) = a;
	const c: (i8 | i16 | i32) = a;
	const d: (i8 | i16 | i8 | i16) = a;
	assert(rt::compile(
	assert(compile(
		// Cannot reduce to a single member
		"fn test() void = {
			let a: (u8 | u8) = 42u8;
		};"
	) != 0);
	assert(rt::compile(
	) as exited != EXIT_SUCCESS);
	assert(compile(
		// Cannot assign from more general type
		"fn test() void = {
			let a: (i8 | i16 | i32) = 42i8;
			let b: (i8 | i16) = a;
		};"
	) != 0);
	) as exited != EXIT_SUCCESS);
	assert(a is i8 && b is i8 && c is i8 && d is i8);
	assert(size((i8 | i16 | i32)) == size((i8 | (i16 | i32))));
	assert(size(integer) == size(signed));
@@ -191,50 +191,50 @@ fn assertions() void = {

fn reject() void = {
	// cannot type assert into a disjoint tagged type
	assert(rt::compile(
	assert(compile(
		"fn test() void = {
			let a: (u8 | u16) = 42u8;
			let b = a as (str | void);
		};"
	) != 0);
	) as exited != EXIT_SUCCESS);

	// cannot type assert into non-member type
	assert(rt::compile(
	assert(compile(
		"fn test() void = {
			let a: (u8 | u16) = 42u8;
			let b = a as *str;
		};"
	) != 0);
	) as exited != EXIT_SUCCESS);

	// cannot type assert into superset
	assert(rt::compile(
	assert(compile(
		"fn test() void = {
			let a: (u8 | u16) = 42u8;
			let b = a as (u8 | u16 | void);
		};"
	) != 0);
	) as exited != EXIT_SUCCESS);

	// cannot type assert into the same type
	assert(rt::compile(
	assert(compile(
		"fn test() void = {
			let a: (u8 | u16) = 42u8;
			let b = a as (u8 | u16);
		};"
	) != 0);
	) as exited != EXIT_SUCCESS);

	// cannot have members of undefined size
	assert(rt::compile(
	assert(compile(
		"fn test() (void | [*]int) = {
			void;
		};"
	) != 0);
	) as exited != EXIT_SUCCESS);

	// cannot have <2 members
	assert(rt::compile(
	assert(compile(
		"fn test() (void | void) = {
			void;
		};"
	) != 0);
	) as exited != EXIT_SUCCESS);
};

export fn main() void = {
diff --git a/tests/15-enums.ha b/tests/15-enums.ha
index a14712e..59d7b2e 100644
--- a/tests/15-enums.ha
+++ b/tests/15-enums.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};
use testmod;

type implicit_values = enum {
@@ -61,21 +61,21 @@ fn storage() void = {

fn reject() void = {
	// enum type definition used outside type declaration
	assert(rt::compile("export let a: enum { A, B } = 0;") != 0);
	assert(rt::compile("export let a: int = 0: enum{A, B}: int;") != 0);
	assert(compile("export let a: enum { A, B } = 0;") as exited != EXIT_SUCCESS);
	assert(compile("export let a: int = 0: enum{A, B}: int;") as exited != EXIT_SUCCESS);

	// enum circular dependencies
	assert(rt::compile("type a = enum { A = B, B = A };") != 0);
	assert(rt::compile("type a = enum { A = b::B },
				b = enum { B = a::A };") != 0);
	assert(rt::compile("
	assert(compile("type a = enum { A = B, B = A };") as exited != EXIT_SUCCESS);
	assert(compile("type a = enum { A = b::B },
				b = enum { B = a::A };") as exited != EXIT_SUCCESS);
	assert(compile("
		def a: int = e::VAL1;
		type e = enum { VAL1 = a };
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		def a: int = e::VAL1;
		type e = enum { VAL1 = VAL2, VAL2 = a };
	") != 0);
	") as exited != EXIT_SUCCESS);
};

type interdependent1 = enum {
diff --git a/tests/19-append.ha b/tests/19-append.ha
index dd65f13..6ecfae5 100644
--- a/tests/19-append.ha
+++ b/tests/19-append.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

fn basics() void = {
	let x: []int = [];
@@ -68,33 +68,33 @@ fn typehints() void = {
};

fn reject() void = {
	assert(rt::compile("
	assert(compile("
		let x: []u8 = [0u8];
		let y: int = 42;
		append(x, y);
	") != 0); // object member type != value type
	assert(rt::compile("
	") as exited != EXIT_SUCCESS); // object member type != value type
	assert(compile("
		let x: []u8 = [0u8];
		let y = 42u8;
		append(x, y...);
	") != 0); // value is not an array or a slice
	assert(rt::compile("
	") as exited != EXIT_SUCCESS); // value is not an array or a slice
	assert(compile("
		let x: []u8 = [0u8];
		let y: []int = [42];
		append(x, y...);
	") != 0); // object member type != value member type
	assert(rt::compile("
	") as exited != EXIT_SUCCESS); // object member type != value member type
	assert(compile("
		let x: []u8 = [0u8];
		append(x, [42i...], 5);
	") != 0); // same as above, but for an expression with length
	assert(rt::compile("
	") as exited != EXIT_SUCCESS); // same as above, but for an expression with length
	assert(compile("
		let x: []u8 = [0u8];
		append(x, [0u8...], 2i);
	") != 0); // length expression is not assignable to size
	assert(rt::compile("
	") as exited != EXIT_SUCCESS); // length expression is not assignable to size
	assert(compile("
		let x: []u8 = [0u8];
		append(x, [42], 3);
	") != 0); // must be an expandable array
	") as exited != EXIT_SUCCESS); // must be an expandable array
};

export fn main() void = {
diff --git a/tests/21-tuples.ha b/tests/21-tuples.ha
index d1e9865..3bb5b5f 100644
--- a/tests/21-tuples.ha
+++ b/tests/21-tuples.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

fn storage() void = {
	let x: (int, size) = (42, 1337);
@@ -112,38 +112,38 @@ fn unpacking() void = {
		unpacking_addone(), unpacking_addone());
	assert(a == 1 && b == 2 && d == 4);

	assert(rt::compile("
	assert(compile("
		export fn main() void = {
			let (_, _) = (1, 2);
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		export fn main() void = {
			let (x, y) = (1, 2, 3);
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		export fn main() void = {
			let (x, y) = 5;
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		fn getval() int = 5;
		export fn main() void = {
			static let (a, b) = (2, getval());
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		fn getval() int = 5;
		export fn main() void = {
			static let (a, _) = (2, getval());
		};
	") != 0);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		export fn main() void = {
			let (a, b): int = (2, 3);
		};
	") != 0);
	") as exited != EXIT_SUCCESS);
};

// Regression tests for miscellaneous compiler bugs
diff --git a/tests/23-errors.ha b/tests/23-errors.ha
index 51f9ea4..4cb1a85 100644
--- a/tests/23-errors.ha
+++ b/tests/23-errors.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

type err_int = !int;

@@ -29,37 +29,43 @@ fn propagate() void = {
};

fn cannotignore() void = {
	assert(rt::compile("type error = void!;

export fn main() int = {
	error;
	return 42;
};") != 0);
	assert(compile("
		type error = !void;

		export fn main() int = {
			error;
			return 42;
		};
	") as exited != EXIT_SUCCESS);
	err_if_false(true)!;
};

fn void_assignability() void = {
	assert(rt::compile(`type err = !void;
	assert(compile(`
		type err = !void;

fn reterr() (int | err) = {
	return err;
};
		fn reterr() (int | err) = {
			return err;
		};

fn properr() void = {
	reterr()?;
};
		fn properr() void = {
			reterr()?;
		};

export fn main() void = void;`) != 0); // error types cannot be assigned to void
		export fn main() void = void;
	`) as exited != EXIT_SUCCESS); // error types cannot be assigned to void

	assert(rt::compile(`fn disallow_1() void = {
	return "I am illegal";
};
	assert(compile(`
		fn disallow_1() void = {
			return "I am illegal";
		};

fn disallow_2() void = {
	return 12;
};
		fn disallow_2() void = {
			return 12;
		};

export fn main() void = void;`) != 0); // non-void types cannot be assigned to void
		export fn main() void = void;
	`) as exited != EXIT_SUCCESS); // non-void types cannot be assigned to void
};

export fn main() void = {
diff --git a/tests/24-imports.ha b/tests/24-imports.ha
index 7b4c1aa..dc20d47 100644
--- a/tests/24-imports.ha
+++ b/tests/24-imports.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};
use testmod;

fn _enum() void = {
@@ -7,80 +7,80 @@ fn _enum() void = {
};

fn accept() void = {
	assert(rt::compile("
	assert(compile("
		use testmod;
		export fn main() void = static assert(testmod::val == 42);
	") == rt::EXIT_SUCCESS);
	assert(rt::compile("
	") as exited == EXIT_SUCCESS);
	assert(compile("
		use testmod;
		use alias = testmod;
		export fn main() void = static assert(testmod::val == alias::val);
	") == rt::EXIT_SUCCESS);
	assert(rt::compile("
	") as exited == EXIT_SUCCESS);
	assert(compile("
		use testmod;
		use testmod::{val, val2};
		export fn main() void = static assert(
			testmod::val == val && testmod::val2 == val2
		);
	") == rt::EXIT_SUCCESS);
	assert(rt::compile("
	") as exited == EXIT_SUCCESS);
	assert(compile("
		use testmod;
		use testmod::*;
		export fn main() void = static assert(
			testmod::val == val && testmod::val2 == val2
		);
	") == rt::EXIT_SUCCESS);
	assert(rt::compile("
	") as exited == EXIT_SUCCESS);
	assert(compile("
		use testmod;
		use testmod::{alias = val, val2};
		export fn main() void = static assert(
			testmod::val == alias && testmod::val2 == val2
		);
	") == rt::EXIT_SUCCESS);
	assert(rt::compile("
	") as exited == EXIT_SUCCESS);
	assert(compile("
		use testmod;
		use modalias = testmod::{valalias = val, val2};
		export fn main() void = static assert(
			testmod::val == modalias::valalias && testmod::val2 == modalias::val2
		);
	") == rt::EXIT_SUCCESS);
	") as exited == EXIT_SUCCESS);
};

fn reject() void = {
	assert(rt::compile("
	assert(compile("
		use wrong;
		export fn main() void = { testmod::val };
	") == rt::EXIT_FAILURE);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		use testmod::{val};
		export fn main() void = static assert(
			testmod::val == 42
		);
	") == rt::EXIT_FAILURE);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		use testmod::{val};
		export fn main() void = static assert(
			val2 == 90
		);
	") == rt::EXIT_FAILURE);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		use testmod;
		use test = testmod::*;
		export fn main() void = void;
	") == rt::EXIT_FAILURE);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		use testmod;
		use testmod*;
		export fn main() void = void;
	") == rt::EXIT_FAILURE);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		use testmod::{alias = val, val2};
		export fn main() void = static assert(val == 42);
	") == rt::EXIT_FAILURE);
	assert(rt::compile("
	") as exited != EXIT_SUCCESS);
	assert(compile("
		use modalias = testmod::{valalias = val, val2};
		export fn main() void = static assert(valalias == 42);
	") == rt::EXIT_FAILURE);
	") as exited != EXIT_SUCCESS);
};


diff --git a/tests/27-rt.ha b/tests/27-rt.ha
index 689ef87..eec32b9 100644
--- a/tests/27-rt.ha
+++ b/tests/27-rt.ha
@@ -7,12 +7,12 @@ fn compile() void = {
		fn test() void = {
			void;
		};"
	) == 0);
	) as rt::exited == rt::EXIT_SUCCESS);
	assert(rt::compile("
		fn test() void = {
			let a: int = [1, 2, 3];
		};"
	) != 0);
	) as rt::exited != rt::EXIT_SUCCESS);
};

export fn main() void = {
diff --git a/tests/28-insert.ha b/tests/28-insert.ha
index 4168be2..e0648ea 100644
--- a/tests/28-insert.ha
+++ b/tests/28-insert.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

fn basics() void = {
	let x: []int = alloc([1, 2, 5]);
@@ -62,26 +62,26 @@ fn typehints() void = {
};

fn reject() void = {
	assert(rt::compile("
	assert(compile("
		let x: []u8 = [0u8];
		let y: int = 42;
		insert(x[1], y);
	") != 0); // object member type != value type
	assert(rt::compile("
	") as exited != EXIT_SUCCESS); // object member type != value type
	assert(compile("
		let x: []u8 = [0u8];
		let y = 42u8;
		insert(x[1], y...);
	") != 0); // value is not an array or a slice
	assert(rt::compile("
	") as exited != EXIT_SUCCESS); // value is not an array or a slice
	assert(compile("
		let x: []u8 = [0u8];
		let y: []int = [42];
		insert(x[1], y...);
	") != 0); // object member type != value member type
	assert(rt::compile("
	") as exited != EXIT_SUCCESS); // object member type != value member type
	assert(compile("
		let x: []u8 = [0u8];
		let y: []int = [42];
		insert(x[1], [y...], 3);
	") != 0); // insert expression doesn't have a length parameter
	") as exited != EXIT_SUCCESS); // insert expression doesn't have a length parameter
};

export fn main() void = {
diff --git a/tests/34-declarations.ha b/tests/34-declarations.ha
index da2b4ab..aabb847 100644
--- a/tests/34-declarations.ha
+++ b/tests/34-declarations.ha
@@ -1,4 +1,4 @@
use rt;
use rt::{compile, exited, EXIT_SUCCESS};

// interdependent constants
def A1: int = -1;
@@ -206,16 +206,16 @@ fn sz() void = {

fn reject() void = {
	// TODO: figure out a better way to test these
	assert(rt::compile("type a = b; type b = a;") != 0);
	assert(rt::compile("type a = [20]a;") != 0);
	assert(rt::compile("type a = b; type b = a;") != 0);
	assert(rt::compile("type a = [20]a;") != 0);
	assert(rt::compile("type a = unknown;") != 0);
	assert(rt::compile("def x: int = 6; type a = x;") != 0);
	assert(rt::compile("type a = int; type a = str;") != 0);
	assert(rt::compile("def a: int = b; def b: int = a;") != 0);
	assert(rt::compile("def x: size = size(t); type t = [x]int;") != 0);
	assert(rt::compile("def a: int = 12; type t = (int |(...a | a));") != 0);
	assert(compile("type a = b; type b = a;") as exited != EXIT_SUCCESS);
	assert(compile("type a = [20]a;") as exited != EXIT_SUCCESS);
	assert(compile("type a = b; type b = a;") as exited != EXIT_SUCCESS);
	assert(compile("type a = [20]a;") as exited != EXIT_SUCCESS);
	assert(compile("type a = unknown;") as exited != EXIT_SUCCESS);
	assert(compile("def x: int = 6; type a = x;") as exited != EXIT_SUCCESS);
	assert(compile("type a = int; type a = str;") as exited != EXIT_SUCCESS);
	assert(compile("def a: int = b; def b: int = a;") as exited != EXIT_SUCCESS);
	assert(compile("def x: size = size(t); type t = [x]int;") as exited != EXIT_SUCCESS);
	assert(compile("def a: int = 12; type t = (int |(...a | a));") as exited != EXIT_SUCCESS);
};

// Types t_0 to t_9 form a complete directed graph on 10 vertices.
-- 
2.36.1
harec/patches: SUCCESS in 43s

[unify type validity checks for tagged unions][0] v2 from [Bor Grošelj Simić][1]

[0]: https://lists.sr.ht/~sircmpwn/hare-dev/patches/33267
[1]: mailto:bgs@turminal.net

✓ #787344 SUCCESS harec/patches/alpine.yml  https://builds.sr.ht/~sircmpwn/job/787344
✓ #787345 SUCCESS harec/patches/freebsd.yml https://builds.sr.ht/~sircmpwn/job/787345