~sircmpwn/hare-dev

hare: hare::types: a few fixes. v1 PROPOSED

Rosie K Languet: 1
 hare::types: a few fixes.

 6 files changed, 462 insertions(+), 463 deletions(-)
#1098245 alpine.yml success
#1098246 freebsd.yml success
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/46880/mbox | git am -3
Learn more about email & git

[PATCH hare v1] hare::types: a few fixes. Export this patch

fixes an incorrect and untested builtin_never which previously had its
id copy-pasted from builtin_opaque.

removes redundant and divergent computations for arch-independent
builtins from fromast().

remove a missleading and unreachable branch from dealias().

Signed-off-by: Rosie K Languet <rkl@rosiesworkshop.net>
---
For the last several months I've been casually working on the many
TODOs in hare::{unit,types}. The curious can see my work over at [1],
but it's mostly not ready to send here.
This commit contains some of the very first changes I made as I started
poking arround. Having rebasing it mindlessly I don't know how many 
times now, I figure I should just send it before it gets too stale.

I expect this will need many revisions.. Thanks in advance for your
feedback!

[1]: https://git.sr.ht/~roselandgoose/hare

 hare/types/+test.ha    |  11 +-
 hare/types/builtins.ha |   2 +-
 hare/types/from_ast.ha | 387 ++++++++++++++++++++++++++++++++++
 hare/types/lookup.ha   |  24 ---
 hare/types/store.ha    | 462 +++--------------------------------------
 hare/types/types.ha    |  39 ++++
 6 files changed, 462 insertions(+), 463 deletions(-)
 create mode 100644 hare/types/from_ast.ha
 delete mode 100644 hare/types/lookup.ha

diff --git a/hare/types/+test.ha b/hare/types/+test.ha
index cf01f9eb..158c2009 100644
--- a/hare/types/+test.ha
+++ b/hare/types/+test.ha
@@ -339,12 +339,11 @@ fn resolve(
	let st = store(x86_64, &resolve, null);
	defer store_free(st);

	const of = lookup_builtin(st, ast::builtin_type::U64);
	const al = newalias(st, ["myalias"], of);
	const al = newalias(st, ["myalias"], &builtin_u64);
	assert(al.sz == 8);
	assert(al._align == 8);
	assert(al.flags == 0);
	assert((al.repr as alias).secondary == of);
	assert((al.repr as alias).secondary == &builtin_u64);

	const atype = parse_type("myalias");
	defer ast::type_finish(&atype);
@@ -361,12 +360,11 @@ fn resolve(
	const htype = lookup(st, &atype)!;
	assert((htype.repr as alias).secondary == null);

	const of = lookup_builtin(st, ast::builtin_type::U64);
	const al = newalias(st, ["myalias"], of);
	const al = newalias(st, ["myalias"], &builtin_u64);
	assert(htype.sz == 8);
	assert(htype._align == 8);
	assert(htype.flags == 0);
	assert((htype.repr as alias).secondary == of);
	assert((htype.repr as alias).secondary == &builtin_u64);
};

@test fn builtins() void = {
@@ -379,6 +377,7 @@ fn resolve(
		(&builtin_i32, "i32"),
		(&builtin_i64, "i64"),
		(&builtin_opaque, "opaque"),
		(&builtin_never, "never"),
		(&builtin_rune, "rune"),
		(&builtin_u8, "u8"),
		(&builtin_u16, "u16"),
diff --git a/hare/types/builtins.ha b/hare/types/builtins.ha
index 12ebd04d..93eae261 100644
--- a/hare/types/builtins.ha
+++ b/hare/types/builtins.ha
@@ -72,7 +72,7 @@ export const builtin_opaque: _type = _type {
export const builtin_never: _type = _type {
	repr = builtin::NEVER,
	sz = SIZE_UNDEFINED, _align = SIZE_UNDEFINED,
	id = 2374983655,
	id = 1099590421,
	...
};

diff --git a/hare/types/from_ast.ha b/hare/types/from_ast.ha
new file mode 100644
index 00000000..aaf7db2f
--- /dev/null
+++ b/hare/types/from_ast.ha
@@ -0,0 +1,387 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use hare::ast;
use sort;
use strings;

// constructs a [[_type]] from a *[[ast::_type]].
fn fromast(store: *typestore, atype: *ast::_type) (_type | deferred | error) = {
	let sz = SIZE_UNDEFINED, _align = SIZE_UNDEFINED;
	const f = atype.flags: flag;
	const repr = match (atype.repr) {
	case let a: ast::alias_type =>
		// TODO: This is incomplete
		assert(!a.unwrap);
		yield alias {
			id = ast::ident_dup(a.ident),
			secondary = null,
		};
	case let b: ast::builtin_type =>
		yield switch (b) {
		case ast::builtin_type::BOOL => return with_flags(&builtin_bool, f);
		case ast::builtin_type::F32 => return with_flags(&builtin_f32, f);
		case ast::builtin_type::F64 => return with_flags(&builtin_f64, f);
		case ast::builtin_type::I8 => return with_flags(&builtin_i8, f);
		case ast::builtin_type::I16 => return with_flags(&builtin_i16, f);
		case ast::builtin_type::I32 => return with_flags(&builtin_i32, f);
		case ast::builtin_type::I64 => return with_flags(&builtin_i64, f);
		case ast::builtin_type::NEVER => return with_flags(&builtin_never, f);
		case ast::builtin_type::OPAQUE => return with_flags(&builtin_opaque, f);
		case ast::builtin_type::RUNE => return with_flags(&builtin_rune, f);
		case ast::builtin_type::U8 => return with_flags(&builtin_u8, f);
		case ast::builtin_type::U16 => return with_flags(&builtin_u16, f);
		case ast::builtin_type::U32 => return with_flags(&builtin_u32, f);
		case ast::builtin_type::U64 => return with_flags(&builtin_u64, f);
		case ast::builtin_type::VOID => return with_flags(&builtin_void, f);

		// TODO: Tuple unpacking could improve this

		case ast::builtin_type::ICONST, ast::builtin_type::FCONST,
			ast::builtin_type::RCONST =>
			abort(); // TODO?
		case ast::builtin_type::INT =>
			sz = store.arch._int;
			_align = store.arch._int;
			yield builtin::INT;
		case ast::builtin_type::UINT =>
			sz = store.arch._int;
			_align = store.arch._int;
			yield builtin::UINT;
		case ast::builtin_type::SIZE =>
			sz = store.arch._size;
			_align = store.arch._size;
			yield builtin::SIZE;
		case ast::builtin_type::UINTPTR =>
			sz = store.arch._pointer;
			_align = store.arch._pointer;
			yield builtin::UINTPTR;
		case ast::builtin_type::NULL =>
			sz = store.arch._pointer;
			_align = store.arch._pointer;
			yield builtin::NULL;
		case ast::builtin_type::STR =>
			sz = store.arch._pointer;
			sz += sz % store.arch._size + store.arch._size;
			sz += store.arch._size;
			_align = if (store.arch._size > store.arch._pointer)
					store.arch._size
				else
					store.arch._pointer;
			yield builtin::STR;
		case ast::builtin_type::VALIST =>
			sz = store.arch.valist_size;
			_align = store.arch.valist_align;
			yield builtin::VALIST;
		};
	case let f: ast::func_type =>
		yield func_from_ast(store, &f)?;
	case let p: ast::pointer_type =>
		sz = store.arch._pointer;
		_align = store.arch._pointer;
		yield pointer {
			referent = lookup(store, p.referent)?,
			flags = p.flags: pointer_flag,
		};
	case let st: ast::struct_type =>
		let st = struct_from_ast(store, st, false)?;
		sz = 0; _align = 0;
		for (let i = 0z; i < len(st.fields); i += 1) {
			const field = st.fields[i];
			if (field.offs + field._type.sz > sz) {
				sz = field.offs + field._type.sz;
			};
			if (field._type._align > _align) {
				_align = field._type._align;
			};
		};
		yield st;
	case let un: ast::union_type =>
		let st = struct_from_ast(store, un, true)?;
		sz = 0; _align = 0;
		for (let i = 0z; i < len(st.fields); i += 1) {
			const field = st.fields[i];
			if (field.offs + field._type.sz > sz) {
				sz = field.offs + field._type.sz;
			};
			if (field._type._align > _align) {
				_align = field._type._align;
			};
		};
		yield st;
	case let ta: ast::tagged_type =>
		let ta = tagged_from_ast(store, ta)?;
		const sz_align = tagged_complete(store, ta);
		sz = sz_align.0; _align = sz_align.1;
		yield ta;
	case let tu: ast::tuple_type =>
		let tu = tuple_from_ast(store, tu)?;
		sz = 0; _align = 0;
		for (let i = 0z; i < len(tu); i += 1) {
			const value = tu[i];
			if (value.offs + value._type.sz > sz) {
				sz = value.offs + value._type.sz;
			};
			if (value._type._align > _align) {
				_align = value._type._align;
			};
		};
		yield tu;
	case let lt: ast::list_type =>
		let r = list_from_ast(store, &lt)?;
		sz = r.0;
		_align = r.1;
		yield r.2;
	case let et: ast::enum_type =>
		abort(); // TODO
	};
	if (sz != SIZE_UNDEFINED && sz != 0 && sz % _align != 0) {
		sz += _align - (sz - _align) % _align;
	};
	return _type {
		id = 0, // filled in later
		flags = f,
		repr = repr,
		sz = sz,
		_align = _align,
	};
};

fn func_from_ast(
	store: *typestore,
	ft: *ast::func_type,
) (func | deferred | error) = {
	let f = func {
		result = lookup(store, ft.result)?,
		variadism = switch (ft.variadism) {
		case ast::variadism::NONE =>
			yield variadism::NONE;
		case ast::variadism::C =>
			yield variadism::C;
		case ast::variadism::HARE =>
			yield variadism::HARE;
		},
		params = alloc([], len(ft.params)),
	};
	for (let i = 0z; i < len(ft.params); i += 1) {
		append(f.params, lookup(store, ft.params[i]._type)?);
	};
	return f;
};

fn list_from_ast(
	store: *typestore,
	lt: *ast::list_type
) ((size, size, (slice | array)) | deferred | error) = {
	let sz = SIZE_UNDEFINED, _align = SIZE_UNDEFINED;
	let memb = lookup(store, lt.members)?;
	let t = match (lt.length) {
	case ast::len_slice =>
		sz = store.arch._pointer;
		if (sz % store.arch._size != 0) {
			sz += store.arch._size - (sz % store.arch._size);
		};
		sz += store.arch._size * 2;
		_align = if (store.arch._pointer > store.arch._size)
				store.arch._pointer
			else store.arch._size;
		yield memb: slice;
	case (ast::len_unbounded | ast::len_contextual) =>
		// Note: contextual length is handled by hare::unit when
		// initializing bindings. We treat it like unbounded here and
		// it's fixed up later on.
		_align = memb._align;
		yield array {
			length = SIZE_UNDEFINED,
			member = memb,
		};
	case let ex: *ast::expr =>
		const resolv = match (store.resolve) {
		case null =>
			return noresolver;
		case let r: *resolver =>
			yield r;
		};
		const length = resolv(store.rstate, store, ex)?;
		sz = memb.sz * length;
		assert(sz / length == memb.sz, "overflow");
		_align = memb._align;
		yield array {
			length = length,
			member = memb,
		};
	};
	return (sz, _align, t);
};

fn _struct_from_ast(
	store: *typestore,
	atype: ast::struct_union_type,
	is_union: bool,
	fields: *[]struct_field,
	offs: *size,
) (void | deferred | error) = {
	const nfields = len(fields);
	const membs = match(atype) {
	case let atype: ast::struct_type =>
		yield atype.members;
	case let atype: ast::union_type =>
		yield atype: []ast::struct_member;
	};
	for (let i = 0z; i < len(membs); i += 1) {
		*offs = match (membs[i]._offset) {
		case let ex: *ast::expr =>
			yield match (store.resolve) {
			case null =>
				return noresolver;
			case let res: *resolver =>
				yield res(store.rstate, store, ex)?;
			};
		case null =>
			yield *offs;
		};

		const memb = match (membs[i].member) {
		case let se: ast::struct_embedded =>
			let membs: []ast::struct_member = match (se.repr) {
			case let st: ast::struct_type =>
				yield st.members;
			case let ut: ast::union_type =>
				yield ut;
			case =>
				abort(); // Invariant
			};
			_struct_from_ast(store, membs,
				se.repr is ast::union_type,
				fields, offs)?;
			continue;
		case let se: ast::struct_alias =>
			abort(); // TODO
		case let sf: ast::struct_field =>
			yield sf;
		};

		const _type = lookup(store, memb._type)?;
		if (*offs % _type._align != 0) {
			*offs += _type._align - (*offs % _type._align);
		};

		append(fields, struct_field {
			name = memb.name,
			offs = *offs,
			_type = _type,
		});

		if (!is_union) {
			*offs += _type.sz;
		};
	};

	if (is_union) {
		let max = 0z;
		for (let i = nfields; i < len(fields); i += 1) {
			if (fields[i].offs + fields[i]._type.sz > max) {
				max = fields[i].offs + fields[i]._type.sz;
			};
		};
		*offs = max;
	};
};

fn struct_from_ast(
	store: *typestore,
	atype: ast::struct_union_type,
	is_union: bool,
) (_struct | deferred | error) = {
	let fields: []struct_field = [];
	let offs = 0z;
	_struct_from_ast(store, atype, is_union, &fields, &offs)?;
	sort::sort(fields, size(struct_field), &field_cmp);
	return _struct {
		kind = if (is_union) struct_union::UNION else struct_union::STRUCT,
		fields = fields,
	};
};

fn tagged_collect(
	store: *typestore,
	atype: ast::tagged_type,
	types: *[]const *_type,
) (void | deferred | error) = {
	for (let i = 0z; i < len(atype); i += 1) match (atype[i].repr) {
	case let ta: ast::tagged_type =>
		tagged_collect(store, ta, types)?;
	case =>
		append(types, lookup(store, atype[i])?);
	};
};

fn tagged_complete(store: *typestore, ta: tagged) (size, size) = {
	let sz = 0z, _align = 0z;
	for (let i = 0z; i < len(ta); i += 1) {
		if (ta[i].sz > sz) {
			sz = ta[i].sz;
		};
		if (ta[i]._align > _align) {
			_align = ta[i]._align;
		};
	};
	if (store.arch._int > _align) {
		_align = store.arch._int;
	};
	sz += store.arch._int % _align + store.arch._int;
	return (sz, _align);
};

fn tagged_cmp(a: const *opaque, b: const *opaque) int = {
	const a = a: const **_type, b = b: const **_type;
	return if (a.id < b.id) -1 else if (a.id == b.id) 0 else 1;
};

fn tagged_from_ast(
	store: *typestore,
	atype: ast::tagged_type,
) (tagged | deferred | error) = {
	let types: []const *_type = [];
	//defer! free(types);
	tagged_collect(store, atype, &types)?;
	sort::sort(types, size(const *_type), &tagged_cmp);
	for (let i = 1z; i < len(types); i += 1) {
		if (types[i].id == types[i - 1].id) {
			delete(types[i]);
			i -= 1;
		};
	};
	// TODO: Handle this gracefully
	assert(len(types) > 1);
	return types;
};

fn tuple_from_ast(
	store: *typestore,
	membs: ast::tuple_type,
) (tuple | deferred | error) = {
	let values: []tuple_value = [];
	let offs = 0z;
	for (let i = 0z; i < len(membs); i += 1) {
		const val = membs[i];
		const vtype = lookup(store, val)?;

		if (offs % vtype._align != 0) {
			offs += vtype._align - (offs % vtype._align);
		};

		append(values, tuple_value {
			_type = vtype,
			offs = offs,
		});

		offs += vtype.sz;
	};
	return values;
};

fn field_cmp(a: const *opaque, b: const *opaque) int = {
	const a = a: const *struct_field, b = b: *const struct_field;
	return strings::compare(a.name, b.name);
};
diff --git a/hare/types/lookup.ha b/hare/types/lookup.ha
deleted file mode 100644
index 6c089a12..00000000
--- a/hare/types/lookup.ha
@@ -1,24 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use hare::ast;

// Unwraps a type which may be aliased and returns the underlying type.
export fn dealias(t: *_type) const *_type = {
	for (true) match (t.repr) {
	case let a: alias =>
		t = a.secondary as const *_type;
	case =>
		break;
	};
	return t;
};

// Looks up a built-in type.
export fn lookup_builtin(
	store: *typestore,
	_type: ast::builtin_type,
) const *_type = lookup(store, &ast::_type {
	repr = _type,
	...
})!;
diff --git a/hare/types/store.ha b/hare/types/store.ha
index b61f79c4..4f7647b2 100644
--- a/hare/types/store.ha
+++ b/hare/types/store.ha
@@ -3,8 +3,6 @@

use errors;
use hare::ast;
use sort;
use strings;

export def BUCKETS: size = 65535;

@@ -59,7 +57,7 @@ export fn newalias(
	ident: const ast::ident,
	of: const *_type,
) const *_type = {
	const atype: _type = _type {
	const atype = _type {
		flags = of.flags,
		repr = alias {
			id = ast::ident_dup(ident),
@@ -76,7 +74,7 @@ export fn newalias(
	match (lookup(store, &ast::_type {
		repr = ast::alias_type {
			unwrap = false,
			ident = ident,
			ident = ast::ident_dup(ident),
		},
		...
	})) {
@@ -89,11 +87,7 @@ export fn newalias(
		*ty = atype;
		return ty;
	};

	// Or create a new alias
	let bucket = &store.map[id % BUCKETS];
	append(bucket, atype);
	return &bucket[len(bucket) - 1];
	abort("unreachable");
};

// Returned from [[lookup]] when we are unable to resolve this type, but it does
@@ -121,10 +115,18 @@ export fn lookup(
	store: *typestore,
	ty: *ast::_type,
) (const *_type | deferred | error) = {
	const ty = fromast(store, ty)?;
	return _lookup(store, fromast(store, ty)?);
};

fn _lookup(
	store: *typestore,
	ty: _type,
) (const *_type | deferred | error) = {
	if (ty.flags == 0) match (ty.repr) {
	case let b: builtin =>
		switch (b) {
		case builtin::BOOL =>
			return &builtin_bool;
		case builtin::F32 =>
			return &builtin_f32;
		case builtin::F64 =>
@@ -137,6 +139,8 @@ export fn lookup(
			return &builtin_i32;
		case builtin::I64 =>
			return &builtin_i64;
		case builtin::NEVER =>
			return &builtin_never;
		case builtin::OPAQUE =>
			return &builtin_opaque;
		case builtin::RUNE =>
@@ -160,7 +164,6 @@ export fn lookup(
	let bucket = &store.map[id % BUCKETS];
	for (let i = 0z; i < len(bucket); i += 1) {
		if (bucket[i].id == id) {
			type_finish(&ty);
			return &bucket[i];
		};
	};
@@ -169,427 +172,22 @@ export fn lookup(
	return &bucket[len(bucket) - 1];
};

fn fromast(store: *typestore, atype: *ast::_type) (_type | deferred | error) = {
	let sz = SIZE_UNDEFINED, _align = SIZE_UNDEFINED;
	const repr = match (atype.repr) {
	case let a: ast::alias_type =>
		// TODO: This is incomplete
		assert(!a.unwrap);
		yield alias {
			id = ast::ident_dup(a.ident),
			secondary = null,
		};
	case let b: ast::builtin_type =>
		// TODO: Tuple unpacking could improve this
		yield switch (b) {
		case ast::builtin_type::BOOL =>
			sz = store.arch._int;
			_align = store.arch._int;
			yield builtin::BOOL;
		case ast::builtin_type::F32 =>
			sz = 4; _align = 4;
			yield builtin::F32;
		case ast::builtin_type::F64 =>
			sz = 8; _align = 8;
			yield builtin::F64;
		case ast::builtin_type::I16 =>
			sz = 2; _align = 2;
			yield builtin::I16;
		case ast::builtin_type::I32 =>
			sz = 4; _align = 4;
			yield builtin::I32;
		case ast::builtin_type::I64 =>
			sz = 8; _align = 8;
			yield builtin::I64;
		case ast::builtin_type::I8 =>
			sz = 1; _align = 1;
			yield builtin::I8;
		case ast::builtin_type::INT =>
			sz = store.arch._int;
			_align = store.arch._int;
			yield builtin::INT;
		case ast::builtin_type::RUNE =>
			sz = 4; _align = 4;
			yield builtin::RUNE;
		case ast::builtin_type::SIZE =>
			sz = store.arch._size;
			_align = store.arch._size;
			yield builtin::SIZE;
		case ast::builtin_type::STR =>
			sz = store.arch._pointer;
			sz += sz % store.arch._size + store.arch._size;
			sz += store.arch._size;
			_align = if (store.arch._size > store.arch._pointer)
					store.arch._size
				else
					store.arch._pointer;
			yield builtin::STR;
		case ast::builtin_type::U16 =>
			sz = 2; _align = 2;
			yield builtin::U16;
		case ast::builtin_type::U32 =>
			sz = 4; _align = 4;
			yield builtin::U32;
		case ast::builtin_type::U64 =>
			sz = 8; _align = 8;
			yield builtin::U64;
		case ast::builtin_type::U8 =>
			sz = 1; _align = 1;
			yield builtin::U8;
		case ast::builtin_type::UINT =>
			sz = store.arch._int;
			_align = store.arch._int;
			yield builtin::UINT;
		case ast::builtin_type::UINTPTR =>
			sz = store.arch._pointer;
			_align = store.arch._pointer;
			yield builtin::UINTPTR;
		case ast::builtin_type::VOID =>
			sz = 0; _align = 0;
			yield builtin::VOID;
		case ast::builtin_type::NULL =>
			sz = store.arch._pointer;
			_align = store.arch._pointer;
			yield builtin::NULL;
		case ast::builtin_type::ICONST, ast::builtin_type::FCONST,
			ast::builtin_type::RCONST =>
			abort(); // TODO?
		case ast::builtin_type::OPAQUE =>
			sz = SIZE_UNDEFINED;
			_align = SIZE_UNDEFINED;
			yield builtin::OPAQUE;
		case ast::builtin_type::NEVER =>
			sz = SIZE_UNDEFINED;
			_align = SIZE_UNDEFINED;
			yield builtin::NEVER;
		case ast::builtin_type::VALIST =>
			sz = store.arch.valist_size;
			_align = store.arch.valist_align;
			yield builtin::VALIST;
		};
	case let f: ast::func_type =>
		yield func_from_ast(store, &f)?;
	case let p: ast::pointer_type =>
		sz = store.arch._pointer;
		_align = store.arch._pointer;
		yield pointer {
			referent = lookup(store, p.referent)?,
			flags = p.flags: pointer_flag,
		};
	case let st: ast::struct_type =>
		let st = struct_from_ast(store, st, false)?;
		sz = 0; _align = 0;
		for (let i = 0z; i < len(st.fields); i += 1) {
			const field = st.fields[i];
			if (field.offs + field._type.sz > sz) {
				sz = field.offs + field._type.sz;
			};
			if (field._type._align > _align) {
				_align = field._type._align;
			};
		};
		yield st;
	case let un: ast::union_type =>
		let st = struct_from_ast(store, un, true)?;
		sz = 0; _align = 0;
		for (let i = 0z; i < len(st.fields); i += 1) {
			const field = st.fields[i];
			if (field.offs + field._type.sz > sz) {
				sz = field.offs + field._type.sz;
			};
			if (field._type._align > _align) {
				_align = field._type._align;
			};
		};
		yield st;
	case let ta: ast::tagged_type =>
		let ta = tagged_from_ast(store, ta)?;
		sz = 0; _align = 0;
		for (let i = 0z; i < len(ta); i += 1) {
			if (ta[i].sz > sz) {
				sz = ta[i].sz;
			};
			if (ta[i]._align > _align) {
				_align = ta[i]._align;
			};
		};
		if (store.arch._int > _align) {
			_align = store.arch._int;
		};
		sz += store.arch._int % _align + store.arch._int;
		yield ta;
	case let tu: ast::tuple_type =>
		let tu = tuple_from_ast(store, tu)?;
		sz = 0; _align = 0;
		for (let i = 0z; i < len(tu); i += 1) {
			const value = tu[i];
			if (value.offs + value._type.sz > sz) {
				sz = value.offs + value._type.sz;
			};
			if (value._type._align > _align) {
				_align = value._type._align;
			};
		};
		yield tu;
	case let lt: ast::list_type =>
		let r = list_from_ast(store, &lt)?;
		sz = r.0;
		_align = r.1;
		yield r.2;
	case let et: ast::enum_type =>
		abort(); // TODO
	};
	if (sz != SIZE_UNDEFINED && sz != 0 && sz % _align != 0) {
		sz += _align - (sz - _align) % _align;
	};
	return _type {
		id = 0, // filled in later
		flags = atype.flags: flag,
		repr = repr,
		sz = sz,
		_align = _align,
	};
};

fn func_from_ast(
	store: *typestore,
	ft: *ast::func_type,
) (func | deferred | error) = {
	let f = func {
		result = lookup(store, ft.result)?,
		variadism = switch (ft.variadism) {
		case ast::variadism::NONE =>
			yield variadism::NONE;
		case ast::variadism::C =>
			yield variadism::C;
		case ast::variadism::HARE =>
			yield variadism::HARE;
		},
		params = alloc([], len(ft.params)),
	};
	for (let i = 0z; i < len(ft.params); i += 1) {
		append(f.params, lookup(store, ft.params[i]._type)?);
	};
	return f;
};

fn list_from_ast(
// Looks up a built-in type.
export fn lookup_builtin(
	store: *typestore,
	lt: *ast::list_type
) ((size, size, (slice | array)) | deferred | error) = {
	let sz = SIZE_UNDEFINED, _align = SIZE_UNDEFINED;
	let memb = lookup(store, lt.members)?;
	let t = match (lt.length) {
	case ast::len_slice =>
		sz = store.arch._pointer;
		if (sz % store.arch._size != 0) {
			sz += store.arch._size - (sz % store.arch._size);
		};
		sz += store.arch._size * 2;
		_align = if (store.arch._pointer > store.arch._size)
				store.arch._pointer
			else store.arch._size;
		yield memb: slice;
	case (ast::len_unbounded | ast::len_contextual) =>
		// Note: contextual length is handled by hare::unit when
		// initializing bindings. We treat it like unbounded here and
		// it's fixed up later on.
		_align = memb._align;
		yield array {
			length = SIZE_UNDEFINED,
			member = memb,
		};
	case let ex: *ast::expr =>
		const resolv = match (store.resolve) {
		case null =>
			return noresolver;
		case let r: *resolver =>
			yield r;
		};
		const length = resolv(store.rstate, store, ex)?;
		sz = memb.sz * length;
		assert(sz / length == memb.sz, "overflow");
		_align = memb._align;
		yield array {
			length = length,
			member = memb,
		};
	};
	return (sz, _align, t);
};

fn _struct_from_ast(
	store: *typestore,
	atype: ast::struct_union_type,
	is_union: bool,
	fields: *[]struct_field,
	offs: *size,
) (void | deferred | error) = {
	const nfields = len(fields);
	const membs = match(atype) {
	case let atype: ast::struct_type =>
		yield atype.members;
	case let atype: ast::union_type =>
		yield atype: []ast::struct_member;
	};
	for (let i = 0z; i < len(membs); i += 1) {
		*offs = match (membs[i]._offset) {
		case let ex: *ast::expr =>
			yield match (store.resolve) {
			case null =>
				return noresolver;
			case let res: *resolver =>
				yield res(store.rstate, store, ex)?;
			};
		case null =>
			yield *offs;
		};

		const memb = match (membs[i].member) {
		case let se: ast::struct_embedded =>
			let membs: []ast::struct_member = match (se.repr) {
			case let st: ast::struct_type =>
				yield st.members;
			case let ut: ast::union_type =>
				yield ut;
			case =>
				abort(); // Invariant
			};
			_struct_from_ast(store, membs,
				se.repr is ast::union_type,
				fields, offs)?;
			continue;
		case let se: ast::struct_alias =>
			abort(); // TODO
		case let sf: ast::struct_field =>
			yield sf;
		};

		const _type = lookup(store, memb._type)?;
		if (*offs % _type._align != 0) {
			*offs += _type._align - (*offs % _type._align);
		};

		append(fields, struct_field {
			name = memb.name,
			offs = *offs,
			_type = _type,
		});

		if (!is_union) {
			*offs += _type.sz;
		};
	};

	if (is_union) {
		let max = 0z;
		for (let i = nfields; i < len(fields); i += 1) {
			if (fields[i].offs + fields[i]._type.sz > max) {
				max = fields[i].offs + fields[i]._type.sz;
			};
		};
		*offs = max;
	};
};

fn struct_from_ast(
	store: *typestore,
	atype: ast::struct_union_type,
	is_union: bool,
) (_struct | deferred | error) = {
	let fields: []struct_field = [];
	let offs = 0z;
	_struct_from_ast(store, atype, is_union, &fields, &offs)?;
	sort::sort(fields, size(struct_field), &field_cmp);
	return _struct {
		kind = if (is_union) struct_union::UNION else struct_union::STRUCT,
		fields = fields,
	};
};

fn tagged_collect(
	store: *typestore,
	atype: ast::tagged_type,
	types: *[]const *_type,
) (void | deferred | error) = {
	for (let i = 0z; i < len(atype); i += 1) match (atype[i].repr) {
	case let ta: ast::tagged_type =>
		tagged_collect(store, ta, types)?;
	case =>
		append(types, lookup(store, atype[i])?);
	};
};

fn tagged_cmp(a: const *opaque, b: const *opaque) int = {
	const a = a: const **_type, b = b: const **_type;
	return if (a.id < b.id) -1 else if (a.id == b.id) 0 else 1;
};

fn tagged_from_ast(
	store: *typestore,
	atype: ast::tagged_type,
) (tagged | deferred | error) = {
	let types: []const *_type = [];
	//defer! free(types);
	tagged_collect(store, atype, &types)?;
	sort::sort(types, size(const *_type), &tagged_cmp);
	for (let i = 1z; i < len(types); i += 1) {
		if (types[i].id == types[i - 1].id) {
			delete(types[i]);
			i -= 1;
		};
	};
	// TODO: Handle this gracefully
	assert(len(types) > 1);
	return types;
};

fn tuple_from_ast(
	store: *typestore,
	membs: ast::tuple_type,
) (tuple | deferred | error) = {
	let values: []tuple_value = [];
	let offs = 0z;
	for (let i = 0z; i < len(membs); i += 1) {
		const val = membs[i];
		const vtype = lookup(store, val)?;

		if (offs % vtype._align != 0) {
			offs += vtype._align - (offs % vtype._align);
		};

		append(values, tuple_value {
			_type = vtype,
			offs = offs,
		});

		offs += vtype.sz;
	};
	return values;
};

fn field_cmp(a: const *opaque, b: const *opaque) int = {
	const a = a: const *struct_field, b = b: *const struct_field;
	return strings::compare(a.name, b.name);
};

fn type_finish(t: *_type) void = {
	match (t.repr) {
	case let a: alias =>
		ast::ident_free(a.id);
	case array => void;
	case builtin => void;
	case let e: _enum =>
		free(e.values);
	case let f: func =>
		free(f.params);
	case pointer => void;
	case let s: slice => void;
	case let st: _struct =>
		free(st.fields);
	case let tu: tuple =>
		free(tu);
	case let ta: tagged =>
		free(ta);
	_type: ast::builtin_type,
) const *_type = lookup(store, &ast::_type {
	repr = _type,
	...
})!;

// Used to wrap lookup* calls and abort on any [[deferred]] results.
export fn must(
	res: (const *_type | deferred | error)
) (const *_type | error) = {
	match (res) {
	case deferred => abort("a lookup* call that musn't error did error");
	case let ty: const *_type => return ty;
	case let err: error => return err;
	};
};
diff --git a/hare/types/types.ha b/hare/types/types.ha
index c237f804..d1910998 100644
--- a/hare/types/types.ha
+++ b/hare/types/types.ha
@@ -2,6 +2,7 @@
// (c) Hare authors <https://harelang.org>

use hare::ast;
use sort;

// A type alias.
export type alias = struct {
@@ -120,3 +121,41 @@ export type _type = struct {
	sz: size,
	_align: size,
};

// Unwraps a type which may be aliased and returns the underlying type.
export fn dealias(t: *_type) const *_type = {
	for (true) match (t.repr) {
	case let a: alias =>
		t = a.secondary as const *_type;
	case =>
		break;
	};
	return t;
};

fn type_finish(t: *_type) void = {
	match (t.repr) {
	case let a: alias =>
		ast::ident_free(a.id);
	case array => void;
	case builtin => void;
	case let e: _enum =>
		free(e.values);
	case let f: func =>
		free(f.params);
	case pointer => void;
	case let s: slice => void;
	case let st: _struct =>
		free(st.fields);
	case let tu: tuple =>
		free(tu);
	case let ta: tagged =>
		free(ta);
	};
};

fn with_flags(ty: *_type, flags: flag) _type = {
	let ty = *ty;
	ty.flags = flags;
	return ty;
};
-- 
2.42.1
hare/patches: SUCCESS in 2m30s

[hare::types: a few fixes.][0] from [Rosie K Languet][1]

[0]: https://lists.sr.ht/~sircmpwn/hare-dev/patches/46880
[1]: mailto:rkl@rosiesworkshop.net

✓ #1098246 SUCCESS hare/patches/freebsd.yml https://builds.sr.ht/~sircmpwn/job/1098246
✓ #1098245 SUCCESS hare/patches/alpine.yml  https://builds.sr.ht/~sircmpwn/job/1098245