~sircmpwn/hare-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
3 3

[PATCH hare] all: Rename *_{finish,free} to {finish,free}_*

Details
Message ID
<20230908013642.9633-1-autumnull@posteo.net>
DKIM signature
missing
Download raw message
Patch: +263 -263
Signed-off-by: Autumn! <autumnull@posteo.net>
---
in order to match verb_object
 cmd/hare/build.ha              |   4 +-
 cmd/hare/build/gather.ha       |   2 +-
 cmd/hare/build/types.ha        |  10 +--
 cmd/harec/main.ha              |   6 +-
 cmd/haredoc/doc/color.ha       |   2 +-
 cmd/haredoc/doc/resolve.ha     |   2 +-
 cmd/haredoc/main.ha            |   8 +-
 cmd/haretype/main.ha           |   4 +-
 cmd/ioctlgen/main.ha           |   2 +-
 crypto/aes/+x86_64/ni.ha       |   2 +-
 crypto/aes/aes_ct64.ha         |   2 +-
 crypto/aes/block.ha            |   2 +-
 crypto/aes/rt+test.ha          |   2 +-
 crypto/blowfish/blowfish.ha    |   6 +-
 crypto/cipher/ctr.ha           |   4 +-
 crypto/hmac/sha1.ha            |   4 +-
 crypto/hmac/sha256.ha          |   4 +-
 format/ini/scan.ha             |   4 +-
 fs/types.ha                    |   4 +-
 fs/util.ha                     |   6 +-
 glob/+test.ha                  |   2 +-
 glob/glob.ha                   |   8 +-
 hare/ast/decl.ha               |  24 +++---
 hare/ast/expr.ha               | 136 ++++++++++++++++-----------------
 hare/ast/ident.ha              |   2 +-
 hare/ast/import.ha             |   8 +-
 hare/ast/type.ha               |  34 ++++-----
 hare/ast/unit.ha               |   6 +-
 hare/module/deps.ha            |   6 +-
 hare/parse/+test/ident_test.ha |   8 +-
 hare/parse/+test/loc.ha        |   6 +-
 hare/parse/+test/roundtrip.ha  |   2 +-
 hare/parse/+test/unit_test.ha  |   2 +-
 hare/parse/ident.ha            |   2 +-
 hare/types/+test.ha            |  60 +++++++--------
 hare/types/store.ha            |  10 +--
 hare/unit/+test.ha             |  22 +++---
 hare/unit/unit.ha              |   2 +-
 net/dial/resolve.ha            |   4 +-
 net/dns/decode.ha              |   6 +-
 net/dns/query.ha               |   2 +-
 net/dns/types.ha               |  18 ++---
 net/uri/query.ha               |   6 +-
 os/+freebsd/dirfdfs.ha         |   4 +-
 os/+linux/dirfdfs.ha           |   4 +-
 os/os.ha                       |   2 +-
 regex/+test.ha                 |   6 +-
 regex/README                   |   4 +-
 regex/regex.ha                 |  14 ++--
 time/chrono/timezone.ha        |   6 +-
 unix/passwd/group.ha           |  14 ++--
 unix/passwd/passwd.ha          |  16 ++--
 52 files changed, 263 insertions(+), 263 deletions(-)

diff --git a/cmd/hare/build.ha b/cmd/hare/build.ha
index c08db316..0668e4d3 100644
--- a/cmd/hare/build.ha
+++ b/cmd/hare/build.ha
@@ -35,7 +35,7 @@ fn build(name: str, cmd: *getopt::command) (void | error) = {
		version = build::get_version(os::tryenv("HAREC", "harec"))?,
		...
	};
	defer build::ctx_finish(&ctx);
	defer build::finish_ctx(&ctx);

	if (name == "test") {
		ctx.test = true;
@@ -78,7 +78,7 @@ fn build(name: str, cmd: *getopt::command) (void | error) = {
		case 'l' =>
			append(ctx.libs, opt.1);
		case 'N' =>
			ast::ident_free(ctx.ns);
			ast::free_ident(ctx.ns);
			ctx.ns = [];
			match (parse::identstr(opt.1)) {
			case let id: ast::ident =>
diff --git a/cmd/hare/build/gather.ha b/cmd/hare/build/gather.ha
index 7811375f..665e431a 100644
--- a/cmd/hare/build/gather.ha
+++ b/cmd/hare/build/gather.ha
@@ -14,7 +14,7 @@ export fn gather(ctx: *context, input: str) ([]module::module | error) = {
	};
	const nsubmods = if (ctx.submods) {
		let id: ast::ident = [];
		defer ast::ident_free(id);
		defer ast::free_ident(id);
		yield gather_submodules(&ctx.ctx, &mods, &buf, &id)?;
	} else 0z;

diff --git a/cmd/hare/build/types.ha b/cmd/hare/build/types.ha
index 34461b6a..9ce0ef02 100644
--- a/cmd/hare/build/types.ha
+++ b/cmd/hare/build/types.ha
@@ -85,17 +85,17 @@ export type context = struct {
	hashes: [][NSTAGES]([sha256::SZ]u8 | void),
};

export fn ctx_finish(ctx: *context) void = {
export fn finish_ctx(ctx: *context) void = {
	strings::freeall(ctx.ctx.tags);
	for (let i = 0z; i < len(ctx.defines); i += 1) {
		ast::ident_free(ctx.defines[i].ident);
		ast::type_finish(ctx.defines[i]._type);
		ast::expr_finish(ctx.defines[i].init);
		ast::free_ident(ctx.defines[i].ident);
		ast::finish_type(ctx.defines[i]._type);
		ast::finish_expr(ctx.defines[i].init);
	};
	free(ctx.defines);
	free(ctx.libdirs);
	free(ctx.libs);
	ast::ident_free(ctx.ns);
	ast::free_ident(ctx.ns);
	free(ctx.version);
	module::free_slice(ctx.mods);
	free(ctx.hashes);
diff --git a/cmd/harec/main.ha b/cmd/harec/main.ha
index d3835090..e8dc0954 100644
--- a/cmd/harec/main.ha
+++ b/cmd/harec/main.ha
@@ -53,11 +53,11 @@ export fn main() void = {

	// TODO: Use hare::unit resolver
	const store = types::store(types::x86_64, null, null);
	defer types::store_free(store);
	defer types::free_store(store);

	let subunits: []ast::subunit = [];
	defer for (let i = 0z; i < len(subunits); i += 1) {
		ast::subunit_finish(subunits[i]);
		ast::finish_subunit(subunits[i]);
	};

	for (let i = 0z; i < len(cmd.args); i += 1) {
@@ -90,6 +90,6 @@ export fn main() void = {
	case let u: unit::unit =>
		yield u;
	};
	defer unit::unit_finish(unit);
	defer unit::finish_unit(unit);
	gen(out, store, &unit);
};
diff --git a/cmd/haredoc/doc/color.ha b/cmd/haredoc/doc/color.ha
index 9b8497e0..b3903410 100644
--- a/cmd/haredoc/doc/color.ha
+++ b/cmd/haredoc/doc/color.ha
@@ -52,7 +52,7 @@ fn init_colors() void = {
	defer regex::finish(&expr);

	const matches = regex::findall(&expr, env_colors);
	defer regex::result_freeall(matches);
	defer regex::free_results(matches);

	for (let i = 0z; i < len(matches); i += 1) :colors {
		const (k, v)  = (matches[i][1].content, matches[i][2].content);
diff --git a/cmd/haredoc/doc/resolve.ha b/cmd/haredoc/doc/resolve.ha
index f113d6b5..46a18c4b 100644
--- a/cmd/haredoc/doc/resolve.ha
+++ b/cmd/haredoc/doc/resolve.ha
@@ -143,7 +143,7 @@ fn lookup_remote_enum(ctx: *context, what: ast::ident) (ast::ident | void | erro
	let decls: []ast::decl = [];
	defer {
		for (let i = 0z; i < len(decls); i += 1) {
			ast::decl_finish(decls[i]);
			ast::finish_decl(decls[i]);
		};
		free(decls);
	};
diff --git a/cmd/haredoc/main.ha b/cmd/haredoc/main.ha
index c6919dce..86bf2166 100644
--- a/cmd/haredoc/main.ha
+++ b/cmd/haredoc/main.ha
@@ -131,7 +131,7 @@ fn doc(name: str, cmd: *getopt::command) (void | error) = {

	for (let i = 0z; i < len(srcs.ha); i += 1) {
		let u = doc::scan(srcs.ha[i])?;
		ast::imports_finish(u.imports);
		ast::finish_imports(u.imports);
		append(decls, u.decls...);
	};

@@ -157,7 +157,7 @@ fn doc(name: str, cmd: *getopt::command) (void | error) = {
			if (has_decl(decls[i], decl)) {
				append(new, decls[i]);
			} else {
				ast::decl_finish(decls[i]);
				ast::finish_decl(decls[i]);
			};
		};
		if (len(new) == 0) {
@@ -171,7 +171,7 @@ fn doc(name: str, cmd: *getopt::command) (void | error) = {
	};

	defer for (let i = 0z; i < len(decls); i += 1) {
		ast::decl_finish(decls[i]);
		ast::finish_decl(decls[i]);
	};

	const ctx = doc::context {
@@ -215,7 +215,7 @@ fn parseident(in: str) (ast::ident | parse::error) = {
	// XXX: errdefer
	let success = false;
	let ident: ast::ident = [];
	defer if (!success) ast::ident_free(ident);
	defer if (!success) ast::free_ident(ident);
	let z = 0z;
	for (true) {
		const tok = lex::lex(&lexer)?;
diff --git a/cmd/haretype/main.ha b/cmd/haretype/main.ha
index 4e243a6e..4644aea5 100644
--- a/cmd/haretype/main.ha
+++ b/cmd/haretype/main.ha
@@ -26,7 +26,7 @@ fn typeinfo(
		const lexer = lex::init(&stream, "-");
		defer lex::finish(&lexer);
		const atype = parse::_type(&lexer)?;
		defer ast::type_finish(atype);
		defer ast::finish_type(atype);
		const typ = types::lookup(store, &atype)?;
		unparse::_type(os::stdout, 0, atype)?;
		fmt::println()?;
@@ -79,7 +79,7 @@ export fn main() void = {
	};

	const store = types::store(arch, null, null);
	defer types::store_free(store);
	defer types::free_store(store);
	for (let i = 0z; i < len(cmd.args); i += 1) {
		match (typeinfo(store, cmd.args[i])) {
		case void => void;
diff --git a/cmd/ioctlgen/main.ha b/cmd/ioctlgen/main.ha
index 1aa7c2b8..55ff275c 100644
--- a/cmd/ioctlgen/main.ha
+++ b/cmd/ioctlgen/main.ha
@@ -36,7 +36,7 @@ type ioctl = (dir, rune, u32, const nullable *types::_type);
export fn main() void = {
	// TODO: Configurable arch
	const store = types::store(types::x86_64, null, null);
	defer types::store_free(store);
	defer types::free_store(store);

	for (true) {
		const line = match (bufio::scanline(os::stdin)!) {
diff --git a/crypto/aes/+x86_64/ni.ha b/crypto/aes/+x86_64/ni.ha
index 4da5e584..286a3e97 100644
--- a/crypto/aes/+x86_64/ni.ha
+++ b/crypto/aes/+x86_64/ni.ha
@@ -13,7 +13,7 @@ const x86ni_vtable: cipher::blockvtable = cipher::blockvtable {
	nparallel = 1,
	encrypt = &x86ni_encrypt,
	decrypt = &x86ni_decrypt,
	finish = &block_finish,
	finish = &finish_block,
};

// Checks if the native AES interface is available.
diff --git a/crypto/aes/aes_ct64.ha b/crypto/aes/aes_ct64.ha
index 985b7e53..348dfd5a 100644
--- a/crypto/aes/aes_ct64.ha
+++ b/crypto/aes/aes_ct64.ha
@@ -52,7 +52,7 @@ const ct64_vtable: cipher::blockvtable = cipher::blockvtable {
	nparallel = CT64_NPARALLEL,
	encrypt = &aes_ct64_encrypt,
	decrypt = &aes_ct64_decrypt,
	finish = &block_finish,
	finish = &finish_block,
};

// Initializes the ct64 AES implementation with an encryption key.
diff --git a/crypto/aes/block.ha b/crypto/aes/block.ha
index 1ebc11ec..bcedc01c 100644
--- a/crypto/aes/block.ha
+++ b/crypto/aes/block.ha
@@ -42,7 +42,7 @@ type initfunc = fn(b: *block, key: []u8) void;
// Initializes the AES block with an encryption key.
export fn init(b: *block, key: []u8) void = initfuncptr(b, key);

fn block_finish(b: *cipher::block) void = {
fn finish_block(b: *cipher::block) void = {
	let b = b: *block;
	bytes::zero(b.expkey);
};
diff --git a/crypto/aes/rt+test.ha b/crypto/aes/rt+test.ha
index fffdb8ba..ea607580 100644
--- a/crypto/aes/rt+test.ha
+++ b/crypto/aes/rt+test.ha
@@ -4,7 +4,7 @@
use crypto::cipher;
use bytes;

@test fn rt_finish() void = {
@test fn finish_rt() void = {
	const key: [16]u8 = [
		0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
		0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
diff --git a/crypto/blowfish/blowfish.ha b/crypto/blowfish/blowfish.ha
index d8441de4..e9162553 100644
--- a/crypto/blowfish/blowfish.ha
+++ b/crypto/blowfish/blowfish.ha
@@ -23,7 +23,7 @@ const vtable: cipher::blockvtable = cipher::blockvtable {
	nparallel = 1,
	encrypt = &block_encrypt,
	decrypt = &block_decrypt,
	finish = &block_finish,
	finish = &finish_block,
};

// Initializes a new Blowfish cipher. The user should must call [[init]] or
@@ -143,9 +143,9 @@ fn block_decrypt(c: *cipher::block, dest: []u8, src: []u8) void = {
	dest[7] = r: u8;
};

fn block_finish(cipher: *cipher::block) void = {
fn finish_block(cipher: *cipher::block) void = {
	const cipher = cipher: *state;
	assert(cipher.block.finish == &block_finish);
	assert(cipher.block.finish == &finish_block);
	bytes::zero((&cipher.p: *[*]u8)[..len(cipher.p) * size(u32)]);
	bytes::zero((&cipher.s0: *[*]u8)[..len(cipher.s0) * size(u32)]);
	bytes::zero((&cipher.s1: *[*]u8)[..len(cipher.s1) * size(u32)]);
diff --git a/crypto/cipher/ctr.ha b/crypto/cipher/ctr.ha
index 07be32e2..b4394ab8 100644
--- a/crypto/cipher/ctr.ha
+++ b/crypto/cipher/ctr.ha
@@ -58,7 +58,7 @@ export fn ctr(h: io::handle, b: *block, iv: []u8, buf: []u8) ctr_stream = {
		h = h,
		keybuf = &ctr_keybuf,
		advance = &ctr_advance,
		finish = &ctr_finish,
		finish = &finish_ctr,

		b = b,
		counter = counter,
@@ -108,7 +108,7 @@ fn ctr_advance(s: *xorstream, n: size) void = {
	ctr.xorused += n;
};

fn ctr_finish(s: *xorstream) void = {
fn finish_ctr(s: *xorstream) void = {
	let ctr = s: *ctr_stream;
	bytes::zero(ctr.xorbuf);
	finish(ctr.b);
diff --git a/crypto/hmac/sha1.ha b/crypto/hmac/sha1.ha
index 8112caa9..411eb9ee 100644
--- a/crypto/hmac/sha1.ha
+++ b/crypto/hmac/sha1.ha
@@ -31,7 +31,7 @@ export fn sha1(key: []u8) sha1state = {
		sz = sha1::SZ,
		bsz = sha1::BLOCKSZ,
		sum = &sha1sum,
		finish = &sha1finish,
		finish = &finish_sha1,
		...
	};

@@ -49,7 +49,7 @@ fn sha1sum(mac: *mac::mac, dest: []u8) void = {
	sum(&hm.h, hm.keypad, dest);
};

fn sha1finish(mac: *mac::mac) void = {
fn finish_sha1(mac: *mac::mac) void = {
	let hm = mac: *sha1state;
	bytes::zero(hm.keypad);
	io::close(&hm.h)!;
diff --git a/crypto/hmac/sha256.ha b/crypto/hmac/sha256.ha
index 3ba02980..f5ce9112 100644
--- a/crypto/hmac/sha256.ha
+++ b/crypto/hmac/sha256.ha
@@ -31,7 +31,7 @@ export fn sha256(key: []u8) sha256state = {
		sz = sha256::SZ,
		bsz = sha256::BLOCKSZ,
		sum = &sha256sum,
		finish = &sha256finish,
		finish = &finish_sha256,
		...
	};

@@ -49,7 +49,7 @@ fn sha256sum(mac: *mac::mac, dest: []u8) void = {
	sum(&hm.h, hm.keypad, dest);
};

fn sha256finish(mac: *mac::mac) void = {
fn finish_sha256(mac: *mac::mac) void = {
	let hm = mac: *sha256state;
	bytes::zero(hm.keypad);
	io::close(&hm.h)!;
diff --git a/format/ini/scan.ha b/format/ini/scan.ha
index fa703645..11145d2a 100644
--- a/format/ini/scan.ha
+++ b/format/ini/scan.ha
@@ -33,7 +33,7 @@ export fn finish(sc: *scanner) void = {
// An entry in an INI file: (section, key, value).
export type entry = (const str, const str, const str);

// Duplicates an [[entry]]. Use [[entry_finish]] to get rid of it.
// Duplicates an [[entry]]. Use [[finish_entry]] to get rid of it.
export fn entry_dup(ent: entry) entry = (
	strings::dup(ent.0),
	strings::dup(ent.1),
@@ -41,7 +41,7 @@ export fn entry_dup(ent: entry) entry = (
);

// Frees an [[entry]] previously duplicated with [[entry_dup]].
export fn entry_finish(ent: entry) void = {
export fn finish_entry(ent: entry) void = {
	free(ent.0);
	free(ent.1);
	free(ent.2);
diff --git a/fs/types.ha b/fs/types.ha
index 384da3f3..9d73cd19 100644
--- a/fs/types.ha
+++ b/fs/types.ha
@@ -159,7 +159,7 @@ export type dirent = struct {
	ftype: mode,
};

// Duplicates a [[dirent]] object. Call [[dirent_finish]] to get rid of it
// Duplicates a [[dirent]] object. Call [[finish_dirent]] to get rid of it
// later.
export fn dirent_dup(e: *dirent) dirent = {
	let new = *e;
@@ -169,7 +169,7 @@ export fn dirent_dup(e: *dirent) dirent = {

// Frees memory associated with a [[dirent]] object which was duplicated with
// [[dirent_dup]].
export fn dirent_finish(e: *dirent) void = free(e.name);
export fn finish_dirent(e: *dirent) void = free(e.name);

// Flags to use for opening a file. Not all operating systems support all flags;
// at a minimum, RDONLY, WRONLY, RDWR, and CREATE will be supported.
diff --git a/fs/util.ha b/fs/util.ha
index 45411ae8..92eeea23 100644
--- a/fs/util.ha
+++ b/fs/util.ha
@@ -84,7 +84,7 @@ export fn issocket(mode: mode) bool = mode & IFMT == mode::SOCK;
};

// Reads all entries from a directory. The caller must free the return value
// with [[dirents_free]].
// with [[free_dirents]].
export fn readdir(fs: *fs, path: str) ([]dirent | error) = {
	let i = iter(fs, path)?;
	defer finish(i);
@@ -101,9 +101,9 @@ export fn readdir(fs: *fs, path: str) ([]dirent | error) = {
};

// Frees a slice of [[dirent]]s.
export fn dirents_free(d: []dirent) void = {
export fn free_dirents(d: []dirent) void = {
	for (let i = 0z; i < len(d); i += 1) {
		dirent_finish(&d[i]);
		finish_dirent(&d[i]);
	};
	free(d);
};
diff --git a/glob/+test.ha b/glob/+test.ha
index 5a5eea2a..1ff7b87e 100644
--- a/glob/+test.ha
+++ b/glob/+test.ha
@@ -77,7 +77,7 @@ use strings;
		("fo[o/bar/b]az", true, "fo[o/bar/", "b]az", ""),
	];
	let p = pattern_init();
	defer pattern_free(&p);
	defer free_pattern(&p);
	for (let i = 0z; i < len(cases); i += 1) {
		pattern_parse(&p, cases[i].0, cases[i].1);
		const dir = pattern_dir(&p);
diff --git a/glob/glob.ha b/glob/glob.ha
index 36d40540..51318a9c 100644
--- a/glob/glob.ha
+++ b/glob/glob.ha
@@ -71,8 +71,8 @@ export fn glob(pattern: str, flags: flag...) generator = {

// Frees all memory allocated by the generator.
export fn finish(gen: *generator) void = {
	strstack_free(&gen.pats);
	pattern_free(&gen.tmpp);
	free_strstack(&gen.pats);
	free_pattern(&gen.tmpp);
};

// Returns a generated pathname. The returned string is valid until [[next]]
@@ -188,7 +188,7 @@ fn pattern_init() pattern = pattern {
	rem = memio::dynamic(),
};

fn pattern_free(p: *pattern) void = {
fn free_pattern(p: *pattern) void = {
	io::close(&p.dir)!;
	io::close(&p.pat)!;
	io::close(&p.rem)!;
@@ -282,7 +282,7 @@ fn strstack_init() strstack = strstack {
	bufc = 0,
};

fn strstack_free(ss: *strstack) void = {
fn free_strstack(ss: *strstack) void = {
	for (let i = 0z; i < len(ss.bufv); i += 1) {
		io::close(&ss.bufv[i])!;
	};
diff --git a/hare/ast/decl.ha b/hare/ast/decl.ha
index 955639d9..d281d739 100644
--- a/hare/ast/decl.ha
+++ b/hare/ast/decl.ha
@@ -64,33 +64,33 @@ export type decl = struct {
};

// Frees resources associated with a declaration.
export fn decl_finish(d: decl) void = {
export fn finish_decl(d: decl) void = {
	free(d.docs);
	match (d.decl) {
	case let g: []decl_global =>
		for (let i = 0z; i < len(g); i += 1) {
			free(g[i].symbol);
			ident_free(g[i].ident);
			type_finish(g[i]._type);
			expr_finish(g[i].init);
			free_ident(g[i].ident);
			finish_type(g[i]._type);
			finish_expr(g[i].init);
		};
		free(g);
	case let t: []decl_type =>
		for (let i = 0z; i < len(t); i += 1) {
			ident_free(t[i].ident);
			type_finish(t[i]._type);
			free_ident(t[i].ident);
			finish_type(t[i]._type);
		};
		free(t);
	case let f: decl_func =>
		free(f.symbol);
		ident_free(f.ident);
		type_finish(f.prototype);
		if (f.body is expr) expr_finish(f.body as expr);
		free_ident(f.ident);
		finish_type(f.prototype);
		if (f.body is expr) finish_expr(f.body as expr);
	case let c: []decl_const =>
		for (let i = 0z; i < len(c); i += 1) {
			ident_free(c[i].ident);
			type_finish(c[i]._type);
			expr_finish(c[i].init);
			free_ident(c[i].ident);
			finish_type(c[i]._type);
			finish_expr(c[i].init);
		};
		free(c);
	};
diff --git a/hare/ast/expr.ha b/hare/ast/expr.ha
index b7d6d61a..e54bf1a6 100644
--- a/hare/ast/expr.ha
+++ b/hare/ast/expr.ha
@@ -436,12 +436,12 @@ export type expr = struct {
};

// Frees resources associated with a Hare [[expr]]ession.
export fn expr_finish(e: (expr | nullable *expr)) void = match (e) {
export fn finish_expr(e: (expr | nullable *expr)) void = match (e) {
case let e: nullable *expr =>
	match (e) {
	case null => void;
	case let e: *expr =>
		expr_finish(*e);
		finish_expr(*e);
		free(e);
	};
case let e: expr =>
@@ -449,42 +449,42 @@ case let e: expr =>
	case let a: access_expr =>
		match (a) {
		case let i: access_identifier =>
			ident_free(i);
			free_ident(i);
		case let i: access_index =>
			expr_finish(i.object);
			expr_finish(i.index);
			finish_expr(i.object);
			finish_expr(i.index);
		case let f: access_field =>
			expr_finish(f.object);
			finish_expr(f.object);
			free(f.field);
		case let t: access_tuple =>
			expr_finish(t.object);
			expr_finish(t.value);
			finish_expr(t.object);
			finish_expr(t.value);
		};
	case let a: align_expr =>
		type_finish(a: *_type);
		finish_type(a: *_type);
	case let a: alloc_expr =>
		expr_finish(a.init);
		expr_finish(a.capacity);
		finish_expr(a.init);
		finish_expr(a.capacity);
	case let a: append_expr =>
		expr_finish(a.object);
		finish_expr(a.object);
		match (a.variadic) {
		case null => void;
		case let v: *expr =>
			expr_finish(v);
			finish_expr(v);
		};
		for (let i = 0z; i < len(a.values); i += 1) {
			expr_finish(a.values[i]);
			finish_expr(a.values[i]);
		};
		free(a.values);
	case let a: assert_expr =>
		expr_finish(a.cond);
		expr_finish(a.message);
		finish_expr(a.cond);
		finish_expr(a.message);
	case let a: assign_expr =>
		expr_finish(a.object);
		expr_finish(a.value);
		finish_expr(a.object);
		finish_expr(a.value);
	case let b: binarithm_expr =>
		expr_finish(b.lvalue);
		expr_finish(b.rvalue);
		finish_expr(b.lvalue);
		finish_expr(b.rvalue);
	case let b: binding_expr =>
		for (let i = 0z; i < len(b.bindings); i += 1) {
			match (b.bindings[i].name) {
@@ -500,24 +500,24 @@ case let e: expr =>
				};
				free(u);
			};
			type_finish(b.bindings[i]._type);
			expr_finish(b.bindings[i].init);
			finish_type(b.bindings[i]._type);
			finish_expr(b.bindings[i].init);
		};
		free(b.bindings);
	case let b: break_expr =>
		free(b);
	case let c: call_expr =>
		expr_finish(c.lvalue);
		finish_expr(c.lvalue);
		for (let i = 0z; i < len(c.args); i += 1) {
			expr_finish(c.args[i]);
			finish_expr(c.args[i]);
		};
		free(c.args);
	case let c: cast_expr =>
		expr_finish(c.value);
		type_finish(c._type);
		finish_expr(c.value);
		finish_type(c._type);
	case let c: compound_expr =>
		for (let i = 0z; i < len(c.exprs); i += 1) {
			expr_finish(c.exprs[i]);
			finish_expr(c.exprs[i]);
		};
		free(c.exprs);
		free(c.label);
@@ -525,14 +525,14 @@ case let e: expr =>
		match (c) {
		case let a: array_constant =>
			for (let i = 0z; i < len(a.values); i += 1) {
				expr_finish(a.values[i]);
				finish_expr(a.values[i]);
			};
			free(a.values);
		case let s: struct_constant =>
			struct_constant_finish(s);
			finish_struct_constant(s);
		case let t: tuple_constant =>
			for (let i = 0z; i < len(t); i += 1) {
				expr_finish(t[i]);
				finish_expr(t[i]);
			};
			free(t);
		case (value | number_constant) => void;
@@ -540,105 +540,105 @@ case let e: expr =>
	case let c: continue_expr =>
		free(c);
	case let d: defer_expr =>
		expr_finish(d: *expr);
		finish_expr(d: *expr);
	case let d: delete_expr =>
		expr_finish(d.object);
		finish_expr(d.object);
	case let e: error_assert_expr =>
		expr_finish(e);
		finish_expr(e);
	case let f: for_expr =>
		expr_finish(f.bindings);
		expr_finish(f.cond);
		expr_finish(f.afterthought);
		expr_finish(f.body);
		finish_expr(f.bindings);
		finish_expr(f.cond);
		finish_expr(f.afterthought);
		finish_expr(f.body);
	case let f: free_expr =>
		expr_finish(f: *expr);
		finish_expr(f: *expr);
	case let i: if_expr =>
		expr_finish(i.cond);
		expr_finish(i.tbranch);
		expr_finish(i.fbranch);
		finish_expr(i.cond);
		finish_expr(i.tbranch);
		finish_expr(i.fbranch);
	case let e: insert_expr =>
		expr_finish(e.object);
		finish_expr(e.object);
		match (e.variadic) {
		case null => void;
		case let v: *expr =>
			expr_finish(v);
			finish_expr(v);
		};
		for (let i = 0z; i < len(e.values); i += 1) {
			expr_finish(e.values[i]);
			finish_expr(e.values[i]);
		};
		free(e.values);
	case let l: len_expr =>
		expr_finish(l: *expr);
		finish_expr(l: *expr);
	case let m: match_expr =>
		expr_finish(m.value);
		finish_expr(m.value);
		for (let i = 0z; i < len(m.cases); i += 1) {
			free(m.cases[i].name);
			type_finish(m.cases[i]._type);
			finish_type(m.cases[i]._type);
			const exprs = m.cases[i].exprs;
			for (let i = 0z; i < len(exprs); i += 1) {
				expr_finish(exprs[i]);
				finish_expr(exprs[i]);
			};
			free(exprs);
		};
		free(m.cases);
		for (let i = 0z; i < len(m.default); i += 1) {
			expr_finish(m.default[i]);
			finish_expr(m.default[i]);
		};
		free(m.default);
	case let o: offset_expr =>
		expr_finish(o: *expr);
		finish_expr(o: *expr);
	case let p: propagate_expr =>
		expr_finish(p);
		finish_expr(p);
	case let r: return_expr =>
		expr_finish(r: *expr);
		finish_expr(r: *expr);
	case let s: size_expr =>
		type_finish(s: *_type);
		finish_type(s: *_type);
	case let s: slice_expr =>
		expr_finish(s.object);
		expr_finish(s.start);
		expr_finish(s.end);
		finish_expr(s.object);
		finish_expr(s.start);
		finish_expr(s.end);
	case let s: switch_expr =>
		expr_finish(s.value);
		finish_expr(s.value);
		for (let i = 0z; i < len(s.cases); i += 1) {
			let opts = s.cases[i].options;
			for (let j = 0z; j < len(opts); j += 1) {
				expr_finish(opts[j]);
				finish_expr(opts[j]);
			};
			free(opts);

			let exprs = s.cases[i].exprs;
			for (let j = 0z; j < len(exprs); j += 1) {
				expr_finish(exprs[j]);
				finish_expr(exprs[j]);
			};
			free(exprs);
		};
		free(s.cases);
	case let u: unarithm_expr =>
		expr_finish(u.operand);
		finish_expr(u.operand);
	case let v: variadic_expr =>
		match (v) {
		case vastart_expr => void;
		case let v: vaarg_expr =>
			expr_finish(v);
			finish_expr(v);
		case let v: vaend_expr =>
			expr_finish(v);
			finish_expr(v);
		};
	case let y: yield_expr =>
		free(y.label);
		expr_finish(y.value);
		finish_expr(y.value);
	};
};

fn struct_constant_finish(s: struct_constant) void = {
	ident_free(s.alias);
fn finish_struct_constant(s: struct_constant) void = {
	free_ident(s.alias);
	for (let i = 0z; i < len(s.fields); i += 1) {
		match (s.fields[i]) {
		case let v: struct_value =>
			free(v.name);
			type_finish(v._type);
			expr_finish(v.init);
			finish_type(v._type);
			finish_expr(v.init);
		case let c: *struct_constant =>
			struct_constant_finish(*c);
			finish_struct_constant(*c);
			free(c);
		};
	};
diff --git a/hare/ast/ident.ha b/hare/ast/ident.ha
index bfe3f211..003d4923 100644
--- a/hare/ast/ident.ha
+++ b/hare/ast/ident.ha
@@ -14,7 +14,7 @@ export type ident = []str;
export def IDENT_MAX: size = 255;

// Frees resources associated with an [[ident]]ifier.
export fn ident_free(ident: ident) void = strings::freeall(ident);
export fn free_ident(ident: ident) void = strings::freeall(ident);

// Returns true if two [[ident]]s are identical.
export fn ident_eq(a: ident, b: ident) bool = {
diff --git a/hare/ast/import.ha b/hare/ast/import.ha
index a5dbd8c6..023315d6 100644
--- a/hare/ast/import.ha
+++ b/hare/ast/import.ha
@@ -26,8 +26,8 @@ export type import = struct {
};

// Frees resources associated with an [[import]].
export fn import_finish(import: import) void = {
	ident_free(import.ident);
export fn finish_import(import: import) void = {
	free_ident(import.ident);
	if (import.mode == import_mode::ALIAS) {
		free(import.alias);
	};
@@ -44,9 +44,9 @@ export fn import_finish(import: import) void = {

// Frees resources associated with each [[import]] in a slice, and then
// frees the slice itself.
export fn imports_finish(imports: []import) void = {
export fn finish_imports(imports: []import) void = {
	for (let i = 0z; i < len(imports); i += 1) {
		import_finish(imports[i]);
		finish_import(imports[i]);
	};
	free(imports);
};
diff --git a/hare/ast/type.ha b/hare/ast/type.ha
index e3c2ee53..337ce1db 100644
--- a/hare/ast/type.ha
+++ b/hare/ast/type.ha
@@ -134,7 +134,7 @@ export type _type = struct {
		tagged_type | tuple_type),
};

fn struct_type_finish(t: (struct_type | union_type)) void = {
fn finish_struct_type(t: (struct_type | union_type)) void = {
	let membs = match (t) {
	case let s: struct_type =>
		yield s.members: []struct_member;
@@ -146,35 +146,35 @@ fn struct_type_finish(t: (struct_type | union_type)) void = {
		match (membs[i]._offset) {
		case null => void;
		case let e: *expr =>
			expr_finish(e);
			finish_expr(e);
		};
		match (membs[i].member) {
		case let f: struct_field =>
			free(f.name);
			type_finish(f._type);
			finish_type(f._type);
		case let e: struct_embedded =>
			type_finish(e: *_type);
			finish_type(e: *_type);
		case let a: struct_alias =>
			ident_free(a);
			free_ident(a);
		};
	};
	free(membs);
};

// Frees resources associated with a [[_type]].
export fn type_finish(t: (_type | nullable *_type)) void = {
export fn finish_type(t: (_type | nullable *_type)) void = {
	match (t) {
	case let t: nullable *_type =>
		match (t) {
		case null => void;
		case let t: *_type =>
			type_finish(*t);
			finish_type(*t);
			free(t);
		};
	case let t: _type =>
		match (t.repr) {
		case let a: alias_type =>
			ident_free(a.ident);
			free_ident(a.ident);
		case builtin_type => void;
		case let e: enum_type =>
			for (let i = 0z; i < len(e.values); i += 1) {
@@ -182,36 +182,36 @@ export fn type_finish(t: (_type | nullable *_type)) void = {
				match (e.values[i].value) {
				case null => void;
				case let v: *expr =>
					expr_finish(v);
					finish_expr(v);
				};
			};
			free(e.values);
		case let f: func_type =>
			type_finish(f.result);
			finish_type(f.result);
			for (let i = 0z; i < len(f.params); i += 1) {
				free(f.params[i].name);
				type_finish(f.params[i]._type);
				finish_type(f.params[i]._type);
			};
			free(f.params);
		case let l: list_type =>
			match (l.length) {
			case let e: *expr =>
				expr_finish(e);
				finish_expr(e);
			case => void;
			};
			type_finish(l.members);
			finish_type(l.members);
		case let p: pointer_type =>
			type_finish(p.referent);
			finish_type(p.referent);
		case let s: (struct_type | union_type) =>
			struct_type_finish(s);
			finish_struct_type(s);
		case let t: tagged_type =>
			for (let i = 0z; i < len(t); i += 1) {
				type_finish(t[i]);
				finish_type(t[i]);
			};
			free(t);
		case let t: tuple_type =>
			for (let i = 0z; i < len(t); i += 1) {
				type_finish(t[i]);
				finish_type(t[i]);
			};
			free(t);
		};
diff --git a/hare/ast/unit.ha b/hare/ast/unit.ha
index 3bed943f..fbb1af4f 100644
--- a/hare/ast/unit.ha
+++ b/hare/ast/unit.ha
@@ -9,10 +9,10 @@ export type subunit = struct {
};

// Frees resources associated with a [[subunit]].
export fn subunit_finish(u: subunit) void = {
	imports_finish(u.imports);
export fn finish_subunit(u: subunit) void = {
	finish_imports(u.imports);
	for (let i = 0z; i < len(u.decls); i += 1) {
		decl_finish(u.decls[i]);
		finish_decl(u.decls[i]);
	};
	free(u.decls);
};
diff --git a/hare/module/deps.ha b/hare/module/deps.ha
index 3b113745..cb4466fb 100644
--- a/hare/module/deps.ha
+++ b/hare/module/deps.ha
@@ -37,7 +37,7 @@ export fn parse_deps(files: str...) ([]ast::ident | error) = {
		let lexer = lex::init(handle, files[i]);
		defer lex::finish(&lexer);
		let imports = parse::imports(&lexer)?;
		defer ast::imports_finish(imports);
		defer ast::finish_imports(imports);

		// dedupe + insertion sort
		for (let i = 0z; i < len(imports); i += 1) {
@@ -185,11 +185,11 @@ fn _gather(
// Free the resources associated with a [[module]].
export fn finish(mod: *module) void = {
	free(mod.name);
	ast::ident_free(mod.ns);
	ast::free_ident(mod.ns);
	free(mod.path);
	finish_srcset(&mod.srcs);
	for (let i = 0z; i < len(mod.deps); i += 1) {
		ast::ident_free(mod.deps[i].1);
		ast::free_ident(mod.deps[i].1);
	};
	free(mod.deps);
};
diff --git a/hare/parse/+test/ident_test.ha b/hare/parse/+test/ident_test.ha
index ffcc272f..b6ff338e 100644
--- a/hare/parse/+test/ident_test.ha
+++ b/hare/parse/+test/ident_test.ha
@@ -25,7 +25,7 @@ use strings;
		let lexer = lex::init(&buf, "<test>");
		defer lex::finish(&lexer);
		let ident = ident(&lexer) as ast::ident;
		defer ast::ident_free(ident);
		defer ast::free_ident(ident);
		assert(len(ident) == 1);
		assert(ident[0] == "foo");
		let tok = lex::lex(&lexer) as lex::token;
@@ -38,7 +38,7 @@ use strings;
		let lexer = lex::init(&buf, "<test>");
		defer lex::finish(&lexer);
		let ident = ident(&lexer) as ast::ident;
		defer ast::ident_free(ident);
		defer ast::free_ident(ident);
		assert(len(ident) == 2);
		assert(ident[0] == "foo" && ident[1] == "bar");
		let tok = lex::lex(&lexer) as lex::token;
@@ -51,7 +51,7 @@ use strings;
		let lexer = lex::init(&buf, "<test>");
		defer lex::finish(&lexer);
		let ident = ident(&lexer) as ast::ident;
		defer ast::ident_free(ident);
		defer ast::free_ident(ident);
		assert(len(ident) == 3);
		assert(ident[0] == "foo" && ident[1] == "bar"
			&& ident[2] == "baz");
@@ -65,7 +65,7 @@ use strings;
		let lexer = lex::init(&buf, "<test>");
		defer lex::finish(&lexer);
		let ident = ident(&lexer) as ast::ident;
		defer ast::ident_free(ident);
		defer ast::free_ident(ident);
		assert(len(ident) == 2);
		assert(ident[0] == "foo" && ident[1] == "bar");
		let tok = lex::lex(&lexer) as lex::token;
diff --git a/hare/parse/+test/loc.ha b/hare/parse/+test/loc.ha
index 98888c5e..ce361df9 100644
--- a/hare/parse/+test/loc.ha
+++ b/hare/parse/+test/loc.ha
@@ -21,7 +21,7 @@ fn expr_testloc(srcs: str...) void = for (let i = 0z; i < len(srcs); i += 1) {
	case let err: error =>
		fmt::fatalf("{}: {}", srcs[i], strerror(err));
	};
	defer ast::expr_finish(exp);
	defer ast::finish_expr(exp);
	let runes = 0z;
	let d = utf8::decode(srcs[i]);
	for (true) match (utf8::next(&d)!) {
@@ -87,7 +87,7 @@ fn expr_testloc(srcs: str...) void = for (let i = 0z; i < len(srcs); i += 1) {
		fmt::errorln(strerror(err))!;
		abort();
	};
	defer ast::expr_finish(exp);
	defer ast::finish_expr(exp);
	assert(exp.start.line == 1 && exp.start.col == 1);
	assert(exp.end.line == 1 && exp.end.col == 13);
	let c = exp.expr as ast::cast_expr;
@@ -111,7 +111,7 @@ fn type_testloc(srcs: str...) void = for (let i = 0z; i < len(srcs); i += 1) {
		fmt::errorln(strerror(err))!;
		abort();
	};
	defer ast::type_finish(typ);
	defer ast::finish_type(typ);
	let runes = 0z;
	let d = utf8::decode(srcs[i]);
	for (true) match (utf8::next(&d)!) {
diff --git a/hare/parse/+test/roundtrip.ha b/hare/parse/+test/roundtrip.ha
index e1f93b53..86bee294 100644
--- a/hare/parse/+test/roundtrip.ha
+++ b/hare/parse/+test/roundtrip.ha
@@ -42,7 +42,7 @@ fn _roundtrip(src: str) str = {
			abort();
		},
	};
	defer ast::subunit_finish(u);
	defer ast::finish_subunit(u);
	let out = memio::dynamic();
	let z = unparse::subunit(&out, u) as size;
	let unsrc = memio::string(&out)!;
diff --git a/hare/parse/+test/unit_test.ha b/hare/parse/+test/unit_test.ha
index 27310da7..9cd36c60 100644
--- a/hare/parse/+test/unit_test.ha
+++ b/hare/parse/+test/unit_test.ha
@@ -79,7 +79,7 @@ fn tup_to_import(tup: import_tuple) ast::import = ast::import {
	let lexer = lex::init(&buf, "<test>");
	defer lex::finish(&lexer);
	let mods = imports(&lexer)!;
	defer ast::imports_finish(mods);
	defer ast::finish_imports(mods);

	let expected: [_]import_tuple = [
		(ast::import_mode::IDENT, ["foo"], "", []),
diff --git a/hare/parse/ident.ha b/hare/parse/ident.ha
index 03d5e63b..68a86a39 100644
--- a/hare/parse/ident.ha
+++ b/hare/parse/ident.ha
@@ -30,7 +30,7 @@ fn ident_trailing(lexer: *lex::lexer) ((ast::ident, bool) | error) = {
		z += len(name);
	};
	if (z > ast::IDENT_MAX) {
		ast::ident_free(ident: ast::ident);
		ast::free_ident(ident: ast::ident);
		return syntaxerr(lex::mkloc(lexer),
			"Identifier exceeds maximum length");
	};
diff --git a/hare/types/+test.ha b/hare/types/+test.ha
index a40d2b69..558b959a 100644
--- a/hare/types/+test.ha
+++ b/hare/types/+test.ha
@@ -21,10 +21,10 @@ fn parse_type(in: str) ast::_type = {

@test fn store() void = {
	let st = store(x86_64, null, null);
	defer store_free(st);
	defer free_store(st);

	let atype = parse_type("int");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.repr as builtin == builtin::INT);
	assert(htype.sz == x86_64._int && htype._align == x86_64._int);
@@ -33,7 +33,7 @@ fn parse_type(in: str) ast::_type = {
	assert(htype == type2, "types should be singletons");

	let atype = parse_type("*int");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == x86_64._pointer && htype._align == x86_64._pointer);
	let htype = htype.repr as pointer;
@@ -54,11 +54,11 @@ fn resolve(

@test fn structs() void = {
	let st = store(x86_64, &resolve, null);
	defer store_free(st);
	defer free_store(st);

	// Basic struct
	let atype = parse_type("struct { x: int, y: int }");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == 8);
	assert(htype._align == 4);
@@ -78,7 +78,7 @@ fn resolve(

	// Basic union
	let atype = parse_type("union { x: int, y: int }");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == 4);
	assert(htype._align == 4);
@@ -98,7 +98,7 @@ fn resolve(

	// Padding
	let atype = parse_type("struct { w: u8, x: u32, y: u8, z: u64 }");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == 24);
	assert(htype._align == 8);
@@ -115,13 +115,13 @@ fn resolve(
	assert(z.offs == 16);

	let atype = parse_type("struct { x: u8, y: size, z: u8 }");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == 24);

	// Sort order
	let atype = parse_type("struct { z: u8, y: u8, x: u8, q: u8 }");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	let stype = htype.repr as _struct;
	assert(stype.fields[0].name == "q");
@@ -139,7 +139,7 @@ fn resolve(
		},
		p: int,
	}");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == 20);
	assert(htype._align == 4);
@@ -165,7 +165,7 @@ fn resolve(
		},
		p: int,
	}");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == 16);
	assert(htype._align == 4);
@@ -193,7 +193,7 @@ fn resolve(
		@offset(16) y: int,
		@offset(32) z: int,
	}");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == 36);
	assert(htype._align == 4);
@@ -208,11 +208,11 @@ fn resolve(

@test fn tuples() void = {
	let st = store(x86_64, &resolve, null);
	defer store_free(st);
	defer free_store(st);

	// Basic case
	let atype = parse_type("(int, int)");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == 8);
	assert(htype._align == 4);
@@ -226,7 +226,7 @@ fn resolve(

	// Padding
	let atype = parse_type("(i8, i32, i8, i64)");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == 24);
	assert(htype._align == 8);
@@ -240,11 +240,11 @@ fn resolve(

@test fn lists() void = {
	let st = store(x86_64, &resolve, null);
	defer store_free(st);
	defer free_store(st);

	// Slice
	let atype = parse_type("[]int");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == 24);
	assert(htype._align == 8);
@@ -253,7 +253,7 @@ fn resolve(

	// Normal array
	let atype = parse_type("[5]i32");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == 4 * 5);
	assert(htype._align == 4);
@@ -263,7 +263,7 @@ fn resolve(

	// Unbounded array
	let atype = parse_type("[*]i32");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == SIZE_UNDEFINED);
	assert(htype._align == 4);
@@ -273,7 +273,7 @@ fn resolve(

	// Contextual array (equivalent to unbounded at this compilation stage)
	let atype = parse_type("[_]i32");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == SIZE_UNDEFINED);
	assert(htype._align == 4);
@@ -284,10 +284,10 @@ fn resolve(

@test fn funcs() void = {
	let st = store(x86_64, &resolve, null);
	defer store_free(st);
	defer free_store(st);

	let atype = parse_type("fn() never");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == SIZE_UNDEFINED);
	assert(htype._align == SIZE_UNDEFINED);
@@ -297,7 +297,7 @@ fn resolve(
	assert(len(f.params) == 0);

	let atype = parse_type("fn(foo: int, bar: str...) int");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == SIZE_UNDEFINED);
	assert(htype._align == SIZE_UNDEFINED);
@@ -311,10 +311,10 @@ fn resolve(

@test fn tagged() void = {
	let st = store(x86_64, &resolve, null);
	defer store_free(st);
	defer free_store(st);

	let atype = parse_type("(int | int | void)");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == st.arch._int * 2);
	assert(htype._align == st.arch._int);
@@ -324,7 +324,7 @@ fn resolve(
	assert(t[1].repr as builtin == builtin::VOID);

	let atype = parse_type("(int | (int | str | void))");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	let htype = lookup(st, &atype)!;
	assert(htype.sz == 32);
	assert(htype._align == 8);
@@ -337,7 +337,7 @@ fn resolve(

@test fn alias() void = {
	let st = store(x86_64, &resolve, null);
	defer store_free(st);
	defer free_store(st);

	const of = lookup_builtin(st, ast::builtin_type::U64);
	const al = newalias(st, ["myalias"], of);
@@ -347,17 +347,17 @@ fn resolve(
	assert((al.repr as alias).secondary == of);

	const atype = parse_type("myalias");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	const htype = lookup(st, &atype)!;
	assert(htype == al);
};

@test fn forwardref() void = {
	let st = store(x86_64, &resolve, null);
	defer store_free(st);
	defer free_store(st);

	const atype = parse_type("myalias");
	defer ast::type_finish(atype);
	defer ast::finish_type(atype);
	const htype = lookup(st, &atype)!;
	assert((htype.repr as alias).secondary == null);

diff --git a/hare/types/store.ha b/hare/types/store.ha
index 5db42276..09a29318 100644
--- a/hare/types/store.ha
+++ b/hare/types/store.ha
@@ -44,10 +44,10 @@ export fn store(
});

// Frees state associated with a [[typestore]].
export fn store_free(store: *typestore) void = {
export fn free_store(store: *typestore) void = {
	for (let i = 0z; i < len(store.map); i += 1) {
		for (let j = 0z; j < len(store.map[i]); j += 1) {
			type_finish(&store.map[i][j]);
			finish_type(&store.map[i][j]);
		};
		free(store.map[i]);
	};
@@ -161,7 +161,7 @@ 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);
			finish_type(&ty);
			return &bucket[i];
		};
	};
@@ -574,10 +574,10 @@ fn field_cmp(a: const *opaque, b: const *opaque) int = {
	return strings::compare(a.name, b.name);
};

fn type_finish(t: *_type) void = {
fn finish_type(t: *_type) void = {
	match (t.repr) {
	case let a: alias =>
		ast::ident_free(a.id);
		ast::free_ident(a.id);
	case array => void;
	case builtin => void;
	case let e: _enum =>
diff --git a/hare/unit/+test.ha b/hare/unit/+test.ha
index 045fa2e3..e9820dc6 100644
--- a/hare/unit/+test.ha
+++ b/hare/unit/+test.ha
@@ -24,7 +24,7 @@ fn mktestctx() context = context {

fn freetestctx(ctx: *context) void = {
	// TODO: Some of this should be in -test
	types::store_free(ctx.store);
	types::free_store(ctx.store);
};

@test fn access() void = {
@@ -39,7 +39,7 @@ fn freetestctx(ctx: *context) void = {
		...
	});
	const aexpr = parse_expr("hello");
	defer ast::expr_finish(aexpr);
	defer ast::finish_expr(aexpr);
	const expr = process_access(&ctx, aexpr)!;
	const access = expr.expr as access;
	const ao = access as access_object;
@@ -51,14 +51,14 @@ fn freetestctx(ctx: *context) void = {
	const ctx = mktestctx();
	defer freetestctx(&ctx);
	const aexpr = parse_expr("{ void; void; void; }");
	defer ast::expr_finish(aexpr);
	defer ast::finish_expr(aexpr);
	const expr = process_compound(&ctx, aexpr)!;
	assert(expr.result.repr as types::builtin == types::builtin::VOID);
	const compound = expr.expr as compound;
	assert(len(compound) == 3);

	const aexpr = parse_expr("{ return; }");
	defer ast::expr_finish(aexpr);
	defer ast::finish_expr(aexpr);
	const expr = process_compound(&ctx, aexpr)!;
	assert(expr.terminates);

@@ -69,28 +69,28 @@ fn freetestctx(ctx: *context) void = {
	const ctx = mktestctx();
	defer freetestctx(&ctx);
	const aexpr = parse_expr("void");
	defer ast::expr_finish(aexpr);
	defer ast::finish_expr(aexpr);
	const expr = process_constant(&ctx, aexpr)!;
	assert(expr.result.repr as types::builtin == types::builtin::VOID);
	const constexpr = expr.expr as constant;
	assert(constexpr is void);

	const aexpr = parse_expr("true");
	defer ast::expr_finish(aexpr);
	defer ast::finish_expr(aexpr);
	const expr = process_constant(&ctx, aexpr)!;
	assert(expr.result.repr as types::builtin == types::builtin::BOOL);
	const constexpr = expr.expr as constant;
	assert(constexpr as bool == true);

	const aexpr = parse_expr("false");
	defer ast::expr_finish(aexpr);
	defer ast::finish_expr(aexpr);
	const expr = process_constant(&ctx, aexpr)!;
	assert(expr.result.repr as types::builtin == types::builtin::BOOL);
	const constexpr = expr.expr as constant;
	assert(constexpr as bool == false);

	const aexpr = parse_expr("null");
	defer ast::expr_finish(aexpr);
	defer ast::finish_expr(aexpr);
	const expr = process_constant(&ctx, aexpr)!;
	assert(expr.result.repr as types::builtin == types::builtin::NULL);
	assert(expr.expr is constant);
@@ -105,7 +105,7 @@ fn freetestctx(ctx: *context) void = {
	for (let i = 0z; i < len(cases); i += 1) {
		const _case = cases[i];
		const aexpr = parse_expr(_case.0);
		defer ast::expr_finish(aexpr);
		defer ast::finish_expr(aexpr);
		const expr = process_constant(&ctx, aexpr)!;
		assert(expr.result.repr as types::builtin == _case.1);
		const constexpr = expr.expr as constant;
@@ -130,7 +130,7 @@ fn freetestctx(ctx: *context) void = {
	const ctx = mktestctx();
	defer freetestctx(&ctx);
	const aexpr = parse_expr("return;");
	defer ast::expr_finish(aexpr);
	defer ast::finish_expr(aexpr);
	const ret_expr = process_return(&ctx, aexpr)!;
	assert(ret_expr.terminates);
	assert(ret_expr.result.repr as types::builtin == types::builtin::VOID);
@@ -138,7 +138,7 @@ fn freetestctx(ctx: *context) void = {
	assert(rval == null);

	const aexpr = parse_expr("return 10;");
	defer ast::expr_finish(aexpr);
	defer ast::finish_expr(aexpr);
	const ret_expr = process_return(&ctx, aexpr)!;
	assert(ret_expr.terminates);
	assert(ret_expr.result.repr as types::builtin == types::builtin::VOID);
diff --git a/hare/unit/unit.ha b/hare/unit/unit.ha
index dca71736..5864ef8e 100644
--- a/hare/unit/unit.ha
+++ b/hare/unit/unit.ha
@@ -28,7 +28,7 @@ export type unit = struct {
	decls: []decl,
};

export fn unit_finish(unit: unit) void = {
export fn finish_unit(unit: unit) void = {
	// TODO
	return;
};
diff --git a/net/dial/resolve.ha b/net/dial/resolve.ha
index 382c8a86..64a98ff3 100644
--- a/net/dial/resolve.ha
+++ b/net/dial/resolve.ha
@@ -142,9 +142,9 @@ fn resolve_addr(addr: str) ([]ip::addr | error) = {
	};

	const resp6 = dns::query(&query6)?;
	defer dns::message_free(resp6);
	defer dns::free_message(resp6);
	const resp4 = dns::query(&query4)?;
	defer dns::message_free(resp4);
	defer dns::free_message(resp4);

	let addrs: []ip::addr = [];
	collect_answers(&addrs, &resp6.answers);
diff --git a/net/dns/decode.ha b/net/dns/decode.ha
index e336fb58..53fd6720 100644
--- a/net/dns/decode.ha
+++ b/net/dns/decode.ha
@@ -13,12 +13,12 @@ type decoder = struct {
};

// Decodes a DNS message, heap allocating the resources necessary to represent
// it in Hare's type system. The caller must use [[message_free]] to free the
// it in Hare's type system. The caller must use [[free_message]] to free the
// return value.
export fn decode(buf: []u8) (*message | format) = {
	let success = false;
	let msg = alloc(message { ... });
	defer if (!success) message_free(msg);
	defer if (!success) free_message(msg);
	let dec = decoder_init(buf);
	decode_header(&dec, &msg.header)?;
	for (let i = 0z; i < msg.header.qdcount; i += 1) {
@@ -440,7 +440,7 @@ fn decode_tsig(dec: *decoder) (rdata | format) = {
fn decode_txt(dec: *decoder) (rdata | format) = {
	let success = false;
	let items: txt = [];
	defer if (!success) bytes_free(items);
	defer if (!success) free_bytes(items);
	for (len(dec.cur) != 0) {
		const ln = decode_u8(dec)?;
		if (len(dec.cur) < ln) {
diff --git a/net/dns/query.ha b/net/dns/query.ha
index 35dd7522..faf8ad4e 100644
--- a/net/dns/query.ha
+++ b/net/dns/query.ha
@@ -16,7 +16,7 @@ use unix::resolvconf;
def timeout: time::duration = 3 * time::SECOND;

// Performs a DNS query using the provided list of DNS servers. The caller must
// free the return value with [[message_free]].
// free the return value with [[free_message]].
//
// If no DNS servers are provided, the system default servers (if any) are used.
export fn query(query: *message, servers: ip::addr...) (*message | error) = {
diff --git a/net/dns/types.ha b/net/dns/types.ha
index fbd0c87e..3639cd66 100644
--- a/net/dns/types.ha
+++ b/net/dns/types.ha
@@ -281,34 +281,34 @@ export type message = struct {
};

// Frees a [[message]] and the resources associated with it.
export fn message_free(msg: *message) void = {
export fn free_message(msg: *message) void = {
	for (let i = 0z; i < len(msg.questions); i += 1) {
		strings::freeall(msg.questions[i].qname);
	};
	free(msg.questions);

	rrecords_free(msg.answers);
	rrecords_free(msg.authority);
	rrecords_free(msg.additional);
	free_rrecords(msg.answers);
	free_rrecords(msg.authority);
	free_rrecords(msg.additional);

	free(msg);
};

fn bytes_free(in: [][]u8) void = {
fn free_bytes(in: [][]u8) void = {
	for (let i = 0z; i < len(in); i += 1) {
		free(in[i]);
	};
	free(in);
};

fn rrecords_free(rrs: []rrecord) void = {
fn free_rrecords(rrs: []rrecord) void = {
	for (let i = 0z; i < len(rrs); i += 1) {
		rrecord_finish(&rrs[i]);
		finish_rrecord(&rrs[i]);
	};
	free(rrs);
};

fn rrecord_finish(rr: *rrecord) void = {
fn finish_rrecord(rr: *rrecord) void = {
	strings::freeall(rr.name);
	match (rr.rdata) {
	case let cn: cname =>
@@ -347,7 +347,7 @@ fn rrecord_finish(rr: *rrecord) void = {
		free(ts.mac);
		free(ts.other_data);
	case let tx: txt =>
		bytes_free(tx: [][]u8);
		free_bytes(tx: [][]u8);
	case => void;
	};
};
diff --git a/net/uri/query.ha b/net/uri/query.ha
index 0e752fa3..2c0ce7ce 100644
--- a/net/uri/query.ha
+++ b/net/uri/query.ha
@@ -8,14 +8,14 @@ export type query_decoder = struct {
};

// Initializes a decoder for a query string. Use [[query_next]] to walk it. The
// caller must call [[query_finish]] once they're done using it.
// caller must call [[finish_query]] once they're done using it.
export fn decodequery(q: const str) query_decoder = query_decoder {
	tokenizer = strings::tokenize(q, "&"),
	bufs = (memio::dynamic(), memio::dynamic()),
};

// Frees resources associated with the [[query_decoder]].
export fn query_finish(dec: *query_decoder) void = {
export fn finish_query(dec: *query_decoder) void = {
	io::close(&dec.bufs.0)!;
	io::close(&dec.bufs.1)!;
};
@@ -65,7 +65,7 @@ export fn encodequery(pairs: [](str, str)) str = {
	defer finish(&u);

	const query = decodequery(u.query);
	defer query_finish(&query);
	defer finish_query(&query);
	const pair = query_next(&query)! as (str, str);
	assert(pair.0 == "search");
	assert(pair.1 == "#risc-v");
diff --git a/os/+freebsd/dirfdfs.ha b/os/+freebsd/dirfdfs.ha
index 637fa51c..2e036bb9 100644
--- a/os/+freebsd/dirfdfs.ha
+++ b/os/+freebsd/dirfdfs.ha
@@ -366,7 +366,7 @@ fn fs_iter(fs: *fs::fs, path: str) (*fs::iterator | fs::error) = {
	let iter = alloc(os_iterator {
		iter = fs::iterator {
			next = &iter_next,
			finish = &iter_finish,
			finish = &finish_iter,
		},
		fd = fd,
		buf = buf[..fs.getdents_bufsz],
@@ -416,7 +416,7 @@ fn iter_next(iter: *fs::iterator) (fs::dirent | void) = {
	};
};

fn iter_finish(iter: *fs::iterator) void = {
fn finish_iter(iter: *fs::iterator) void = {
	let iter = iter: *os_iterator;
	rt::close(iter.fd)!;
	free(iter.buf);
diff --git a/os/+linux/dirfdfs.ha b/os/+linux/dirfdfs.ha
index 8ad59807..bdf52dc8 100644
--- a/os/+linux/dirfdfs.ha
+++ b/os/+linux/dirfdfs.ha
@@ -409,7 +409,7 @@ fn fs_iter(fs: *fs::fs, path: str) (*fs::iterator | fs::error) = {
	let iter = alloc(os_iterator {
		iter = fs::iterator {
			next = &iter_next,
			finish = &iter_finish,
			finish = &finish_iter,
		},
		fd = fd,
		buf = buf[..fs.getdents_bufsz],
@@ -459,7 +459,7 @@ fn iter_next(iter: *fs::iterator) (fs::dirent | void) = {
	};
};

fn iter_finish(iter: *fs::iterator) void = {
fn finish_iter(iter: *fs::iterator) void = {
	let iter = iter: *os_iterator;
	rt::close(iter.fd)!;
	free(iter.buf);
diff --git a/os/os.ha b/os/os.ha
index 2a9412d1..c7e7d104 100644
--- a/os/os.ha
+++ b/os/os.ha
@@ -33,7 +33,7 @@ export fn iter(path: str) (*fs::iterator | fs::error) = fs::iter(cwd, path);
export fn finish(iter: *fs::iterator) void = fs::finish(iter);

// Reads all entries from a directory. The caller must free the return value
// with [[fs::dirents_free]].
// with [[fs::free_dirents]].
export fn readdir(path: str) ([]fs::dirent | fs::error) = fs::readdir(cwd, path);

// Returns file information for a given path. If the target is a symlink,
diff --git a/regex/+test.ha b/regex/+test.ha
index d81e537d..23238a06 100644
--- a/regex/+test.ha
+++ b/regex/+test.ha
@@ -39,7 +39,7 @@ fn run_find_case(
	defer finish(&re);

	const result = find(&re, string);
	defer result_free(result);
	defer free_result(result);
	if (len(result) == 0) {
		if (expected == matchres::MATCH) {
			fmt::errorfln("Expected expression /{}/ to match string \"{}\", but it did not",
@@ -75,7 +75,7 @@ fn run_submatch_case(
	defer finish(&re);

	const result = find(&re, string);
	defer result_free(result);
	defer free_result(result);
	assert(len(result) == len(targets), "Invalid number of captures");
	for (let i = 0z; i < len(targets); i += 1) {
		assert(targets[i] == result[i].content, "Invalid capture");
@@ -113,7 +113,7 @@ fn run_findall_case(
			expr, string)!;
		abort();
	};
	defer result_freeall(results);
	defer free_results(results);

	if (expected == matchres::NOMATCH) {
		fmt::errorfln("Expected expression /{}/ to not match string \"{}\", but it did",
diff --git a/regex/README b/regex/README
index bac0cc57..14704cc3 100644
--- a/regex/README
+++ b/regex/README
@@ -19,7 +19,7 @@ Testing an expression against a string:
Finding a match for an expression in a string:

	const result = regex::find(&re, "Harriet is happy");
	defer regex::result_free(result);
	defer regex::free_result(result);
	for (let i = 0z; i < len(result); i += 1) {
		fmt::printf("{} ", result[i].content)!;
	};
@@ -29,7 +29,7 @@ Finding a match for an expression in a string:
Finding all matches for an expression in a string:

	const results = regex::findall(&re, "Harriet is happy");
	defer regex::result_freeall(results);
	defer regex::free_results(results);
	for (let i = 0z; i < len(results); i += 1) {
		for (let j = 0z; j < len(results[i]); j += 1) {
			fmt::printf("{} ", results[i][j].content)!;
diff --git a/regex/regex.ha b/regex/regex.ha
index 29ee5aae..f719c4a5 100644
--- a/regex/regex.ha
+++ b/regex/regex.ha
@@ -780,7 +780,7 @@ export fn test(re: *regex, string: str) bool = {

// Attempts to match a [[regex]] against a string and returns the longest
// leftmost match as a [[result]]. The caller must free the return value with
// [[result_free]].
// [[free_result]].
export fn find(re: *regex, string: str) result = {
	let strm = memio::fixed(strings::toutf8(string));
	match (search(re, string, &strm, true)) {
@@ -793,7 +793,7 @@ export fn find(re: *regex, string: str) result = {

// Attempts to match a [[regex]] against a string and returns all
// non-overlapping matches as a slice of [[result]]s. The caller must free the
// return value with [[result_freeall]].
// return value with [[free_results]].
export fn findall(re: *regex, string: str) []result = {
	let res: []result = [];
	let str_idx = 0z, str_bytesize = 0z;
@@ -854,7 +854,7 @@ export fn replacen(
	if (len(matches) == 0) {
		return strings::dup(string);
	};
	defer result_freeall(matches);
	defer free_results(matches);

	const bytes = strings::toutf8(string);
	let buf = alloc(bytes[..matches[0][0].start_bytesize]...);
@@ -941,7 +941,7 @@ export fn rawreplacen(re: *regex, string: str, targetstr: str, n: size) str = {
	if (len(matches) == 0) {
		return strings::dup(string);
	};
	defer result_freeall(matches);
	defer free_results(matches);

	const target = strings::toutf8(targetstr);
	const bytes = strings::toutf8(string);
@@ -962,14 +962,14 @@ export fn rawreplacen(re: *regex, string: str, targetstr: str, n: size) str = {
};

// Frees a [[result]].
export fn result_free(s: result) void = {
export fn free_result(s: result) void = {
	free(s);
};

// Frees a slice of [[result]]s.
export fn result_freeall(s: []result) void = {
export fn free_results(s: []result) void = {
	for (let i = 0z; i < len(s); i += 1) {
		result_free(s[i]);
		free_result(s[i]);
	};
	free(s);
};
diff --git a/time/chrono/timezone.ha b/time/chrono/timezone.ha
index 9051b6f9..c9cfbb40 100644
--- a/time/chrono/timezone.ha
+++ b/time/chrono/timezone.ha
@@ -71,10 +71,10 @@ type tzname = struct {
};

// Frees a [[timezone]]. A [[locality]] argument can be passed.
export fn timezone_free(tz: *timezone) void = {
export fn free_timezone(tz: *timezone) void = {
	free(tz.name);
	for (let i = 0z; i < len(tz.zones); i += 1) {
		zone_finish(&tz.zones[i]);
		finish_zone(&tz.zones[i]);
	};
	free(tz.zones);
	free(tz.transitions);
@@ -83,7 +83,7 @@ export fn timezone_free(tz: *timezone) void = {
};

// Frees resources associated with a [[zone]].
export fn zone_finish(z: *zone) void = {
export fn finish_zone(z: *zone) void = {
	free(z.name);
	free(z.abbr);
};
diff --git a/unix/passwd/group.ha b/unix/passwd/group.ha
index e60f2859..49b46afc 100644
--- a/unix/passwd/group.ha
+++ b/unix/passwd/group.ha
@@ -22,7 +22,7 @@ export type grent = struct {
};

// Reads a Unix-like group entry from an [[io::handle]]. The caller must free
// the return value using [[grent_finish]].
// the return value using [[finish_grent]].
export fn nextgr(in: io::handle) (grent | io::EOF | io::error | invalid) = {
	let line = match (bufio::scanline(in)?) {
	case let ln: []u8 =>
@@ -61,7 +61,7 @@ export fn nextgr(in: io::handle) (grent | io::EOF | io::error | invalid) = {
};

// Frees resources associated with [[grent]].
export fn grent_finish(ent: *grent) void = {
export fn finish_grent(ent: *grent) void = {
	free(ent.name);
	free(ent.userlist);
};
@@ -94,7 +94,7 @@ export fn getgroup(name: str) (grent | void) = {
		if (ent.name == name) {
			return ent;
		} else {
			grent_finish(&ent);
			finish_grent(&ent);
		};
	};
};
@@ -127,7 +127,7 @@ export fn getgid(gid: uint) (grent | void) = {
		if (ent.gid == gid) {
			return ent;
		} else {
			grent_finish(&ent);
			finish_grent(&ent);
		};
	};
};
@@ -139,7 +139,7 @@ export fn getgid(gid: uint) (grent | void) = {
		"video:x:986:alex,wmuser"));

	let ent = nextgr(&buf) as grent;
	defer grent_finish(&ent);
	defer finish_grent(&ent);

	assert(ent.name == "root");
	assert(ent.password == "x");
@@ -148,7 +148,7 @@ export fn getgid(gid: uint) (grent | void) = {
	assert(ent.userlist[0] == "root");

	let ent = nextgr(&buf) as grent;
	defer grent_finish(&ent);
	defer finish_grent(&ent);

	assert(ent.name == "mail");
	assert(ent.password == "x");
@@ -156,7 +156,7 @@ export fn getgid(gid: uint) (grent | void) = {
	assert(len(ent.userlist) == 0);

	let ent = nextgr(&buf) as grent;
	defer grent_finish(&ent);
	defer finish_grent(&ent);

	assert(ent.name == "video");
	assert(ent.password == "x");
diff --git a/unix/passwd/passwd.ha b/unix/passwd/passwd.ha
index ae8533a3..9aebbf23 100644
--- a/unix/passwd/passwd.ha
+++ b/unix/passwd/passwd.ha
@@ -28,7 +28,7 @@ export type pwent = struct {
};

// Reads a Unix-like password entry from an [[io::handle]]. The caller must free
// the return value using [[pwent_finish]].
// the return value using [[finish_pwent]].
export fn nextpw(in: io::handle) (pwent | io::EOF | io::error | invalid) = {
	let line = match (bufio::scanline(in)?) {
	case io::EOF =>
@@ -77,7 +77,7 @@ export fn nextpw(in: io::handle) (pwent | io::EOF | io::error | invalid) = {
};

// Frees resources associated with a [[pwent]].
export fn pwent_finish(ent: *pwent) void = {
export fn finish_pwent(ent: *pwent) void = {
	// pwent fields are sliced from one allocated string returned by
	// bufio::scanline. Freeing the first field frees the entire string in
	// one go.
@@ -86,7 +86,7 @@ export fn pwent_finish(ent: *pwent) void = {

// Looks up a user by name in a Unix-like password file. It expects a password
// database file at /etc/passwd. Aborts if that file doesn't exist or is not
// properly formatted. The return value must be freed with [[pwent_finish]].
// properly formatted. The return value must be freed with [[finish_pwent]].
//
// See [[nextpw]] for low-level parsing API.
export fn getuser(username: str) (pwent | void) = {
@@ -113,14 +113,14 @@ export fn getuser(username: str) (pwent | void) = {
		if (ent.username == username) {
			return ent;
		} else {
			pwent_finish(&ent);
			finish_pwent(&ent);
		};
	};
};

// Looks up a user by ID in a Unix-like password file. It expects a password
// database file at /etc/passwd. Aborts if that file doesn't exist or is not
// properly formatted. The return value must be freed with [[pwent_finish]].
// properly formatted. The return value must be freed with [[finish_pwent]].
//
// See [[nextpw]] for low-level parsing API.
export fn getuid(uid: uint) (pwent | void) = {
@@ -147,7 +147,7 @@ export fn getuid(uid: uint) (pwent | void) = {
		if (ent.uid == uid) {
			return ent;
		} else {
			pwent_finish(&ent);
			finish_pwent(&ent);
		};
	};
};
@@ -158,7 +158,7 @@ export fn getuid(uid: uint) (pwent | void) = {
		"alex:x:1001:1001::/home/alex:/bin/zsh"));

	let ent = nextpw(&buf) as pwent;
	defer pwent_finish(&ent);
	defer finish_pwent(&ent);

	assert(ent.username == "sircmpwn");
	assert(ent.password == "x");
@@ -169,7 +169,7 @@ export fn getuid(uid: uint) (pwent | void) = {
	assert(ent.shell == "/bin/mrsh");

	let ent = nextpw(&buf) as pwent;
	defer pwent_finish(&ent);
	defer finish_pwent(&ent);

	assert(ent.username == "alex");
	assert(ent.password == "x");
-- 
2.42.0

[hare/patches] build success

builds.sr.ht <builds@sr.ht>
Details
Message ID
<CVD5HF2KRJ4B.29GYH42PTPQGX@cirno2>
In-Reply-To
<20230908013642.9633-1-autumnull@posteo.net> (view parent)
DKIM signature
missing
Download raw message
hare/patches: SUCCESS in 1m34s

[all: Rename *_{finish,free} to {finish,free}_*][0] from [Autumn!][1]

[0]: https://lists.sr.ht/~sircmpwn/hare-dev/patches/44475
[1]: autumnull@posteo.net

✓ #1054375 SUCCESS hare/patches/freebsd.yml https://builds.sr.ht/~sircmpwn/job/1054375
✓ #1054374 SUCCESS hare/patches/alpine.yml  https://builds.sr.ht/~sircmpwn/job/1054374
Details
Message ID
<CVEA0B50HEPR.LQYWOTZQ63DF@taiga>
In-Reply-To
<20230908013642.9633-1-autumnull@posteo.net> (view parent)
DKIM signature
missing
Download raw message
Hm, I don't like this at all. subject_verb is generally much more
preferrable imho for these functions. NACK
Details
Message ID
<a72aaf13-8498-464d-a42d-269ed29648a0@posteo.net>
In-Reply-To
<CVEA0B50HEPR.LQYWOTZQ63DF@taiga> (view parent)
DKIM signature
missing
Download raw message
On 9/9/23 10:23, Drew DeVault wrote:
> Hm, I don't like this at all. subject_verb is generally much more
> preferrable imho for these functions. NACK
so, the main issue here is grammatical, in that the noun in all these 
function names is the object not the subject -- i.e. it's "free the 
strings" not "the strings free".

that said, i've been writing C bindings for the last little while and 
have come to appreciate the way that the main object of the function is 
written at the start, since then methods for a certain object are all 
named as module_object_* (or module::object_* for hare).

so i think the naming convention should be changed to not say anything 
about subjects or objects, and instead just say something like "all the 
associated functions for a type should be named as module::type_action 
or just module::action if module == type". this way, "action" can just 
be written in plain english like "get_best_match", and doesn't need to 
conform to subject/object/verb.

~Autumn
Reply to thread Export thread (mbox)