hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 2abde1a4ad36f32546b40ccc88f870048467d941
parent 9fb0512d6d628c330b3487f8827ec5dd6e051c13
Author: Sebastian <sebastian@sebsite.pw>
Date:   Tue, 26 Apr 2022 17:58:01 -0400

Allow returning an error from io::closer

Fixes: https://todo.sr.ht/~sircmpwn/hare/568
Signed-off-by: Sebastian <sebastian@sebsite.pw>

Diffstat:
Mbufio/buffered.ha | 20++++++++++----------
Mbufio/memstream.ha | 10+++++-----
Mcmd/hare/plan.ha | 10+++++-----
Mcmd/hare/release.ha | 16++++++++--------
Mcmd/harec/errors.ha | 2+-
Mcmd/harec/gen.ha | 2+-
Mcmd/harec/main.ha | 4++--
Mcmd/haredoc/docstr.ha | 2+-
Mcmd/haredoc/html.ha | 8++++----
Mcmd/haredoc/main.ha | 6+++---
Mcmd/haredoc/tty.ha | 4++--
Mcmd/haretype/main.ha | 2+-
Mcrypto/blake2b/+test.ha | 4++--
Mcrypto/blake2b/blake2b.ha | 2+-
Mcrypto/hmac/hmac.ha | 2+-
Mcrypto/hmac/sha1.ha | 2+-
Mcrypto/hmac/sha256.ha | 2+-
Mcrypto/sha1/sha1.ha | 2+-
Mcrypto/sha256/sha256.ha | 2+-
Mcrypto/sha512/sha512.ha | 2+-
Mencoding/base32/base32.ha | 10+++++-----
Mencoding/base64/base64.ha | 29+++++++++++++++++++++--------
Mencoding/hex/hex.ha | 2+-
Mfs/fs.ha | 14+++++++++++---
Mglob/glob.ha | 4++--
Mhare/lex/lex.ha | 2+-
Mhare/module/manifest.ha | 9++++++---
Mhare/module/scan.ha | 24++++++++++++++++++++----
Mhare/parse/+test/roundtrip.ha | 2+-
Mhare/parse/parse.ha | 2+-
Mhare/unparse/type.ha | 4++--
Mhash/hash.ha | 2+-
Mio/handle.ha | 4++--
Mio/stream.ha | 4++--
Mio/types.ha | 2+-
Mlinux/timerfd/timerfd.ha | 2+-
Mmime/system.ha | 15+++++++++++----
Mnet/dns/query.ha | 4++--
Mnet/uri/parse.ha | 10+++++-----
Mnet/uri/query.ha | 4++--
Mos/+freebsd/stdfd.ha | 2+-
Mos/+linux/stdfd.ha | 2+-
Mos/exec/exec+freebsd.ha | 6+++---
Mos/exec/exec+linux.ha | 6+++---
Mos/exec/types.ha | 4+++-
Mstrio/ops.ha | 6+++---
Mstrio/stream.ha | 4++--
Mtime/chrono/timezone.ha | 2+-
Mtime/chrono/tzdb.ha | 22++++++++++++++++------
Munix/hosts/lookup.ha | 2+-
Munix/passwd/group.ha | 2+-
Munix/passwd/passwd.ha | 2+-
Munix/resolvconf/load.ha | 2+-
53 files changed, 187 insertions(+), 128 deletions(-)

diff --git a/bufio/buffered.ha b/bufio/buffered.ha @@ -118,10 +118,10 @@ export fn isbuffered(in: io::handle) bool = { }; }; -fn buffered_close_static(s: *io::stream) void = { +fn buffered_close_static(s: *io::stream) (void | io::error) = { assert(s.closer == &buffered_close_static); if (s.writer != null) { - flush(s: *bufstream)!; + flush(s: *bufstream)?; }; }; @@ -199,11 +199,11 @@ fn buffered_write(s: *io::stream, buf: const []u8) (size | io::error) = { @test fn buffered_read() void = { let sourcebuf: []u8 = [1, 3, 3, 7]; let source = fixed(sourcebuf, io::mode::READ); - defer io::close(&source); + defer io::close(&source)!; let rbuf: [1024]u8 = [0...]; let f = buffered(&source, rbuf, []); - defer io::close(&f); + defer io::close(&f)!; let buf: [1024]u8 = [0...]; assert(io::read(&f, buf[..2]) as size == 2); @@ -219,7 +219,7 @@ fn buffered_write(s: *io::stream, buf: const []u8) (size | io::error) = { let rbuf: [16]u8 = [0...]; let f = buffered(&source, rbuf, []); - defer io::close(&f); + defer io::close(&f)!; let buf: [32]u8 = [0...]; assert(io::read(&f, buf) as size == 16); @@ -234,11 +234,11 @@ fn buffered_write(s: *io::stream, buf: const []u8) (size | io::error) = { @test fn buffered_write() void = { // Normal case let sink = dynamic(io::mode::WRITE); - defer io::close(&sink); + defer io::close(&sink)!; let wbuf: [1024]u8 = [0...]; let f = buffered(&sink, [], wbuf); - defer io::close(&f); + defer io::close(&f)!; assert(io::write(&f, [1, 3, 3, 7]) as size == 4); assert(len(buffer(&sink)) == 0); @@ -248,7 +248,7 @@ fn buffered_write(s: *io::stream, buf: const []u8) (size | io::error) = { // Test flushing via buffer exhaustion let sink = dynamic(io::mode::WRITE); - defer io::close(&sink); + defer io::close(&sink)!; let wbuf: [4]u8 = [0...]; let f = buffered(&sink, [], wbuf); @@ -257,12 +257,12 @@ fn buffered_write(s: *io::stream, buf: const []u8) (size | io::error) = { assert(len(buffer(&sink)) == 0); assert(io::write(&f, [1, 3, 3, 7]) as size == 4); assert(bytes::equal(buffer(&sink), [1, 3, 3, 7])); - io::close(&f); // Should flush + io::close(&f)!; // Should flush assert(bytes::equal(buffer(&sink), [1, 3, 3, 7, 1, 3, 3, 7])); // Test flushing via flush characters let sink = dynamic(io::mode::WRITE); - defer io::close(&sink); + defer io::close(&sink)!; let wbuf: [1024]u8 = [0...]; let f = buffered(&sink, [], wbuf); diff --git a/bufio/memstream.ha b/bufio/memstream.ha @@ -140,7 +140,7 @@ fn dynamic_write(s: *io::stream, buf: const []u8) (size | io::error) = { return len(buf); }; -fn dynamic_close(s: *io::stream) void = { +fn dynamic_close(s: *io::stream) (void | io::error) = { const s = s: *memstream; free(s.buf); }; @@ -222,7 +222,7 @@ fn copy(dest: *io::stream, src: *io::stream) (io::error | size) = { assert(io::write(&s, [4, 5, 6]) as size == 3); assert(bytes::equal(buffer(&s), [0, 0, 1, 2, 3, 4, 5, 6])); assert(io::read(&s, buf[..]) as io::error is errors::unsupported); - io::close(&s); + io::close(&s)!; sl = alloc([1, 2]); let s = dynamic_from(sl, io::mode::READ); @@ -230,7 +230,7 @@ fn copy(dest: *io::stream, src: *io::stream) (io::error | size) = { assert(io::seek(&s, 1, io::whence::CUR) as io::off == 2: io::off); assert(io::read(&s, buf[..]) is io::EOF); assert(io::write(&s, [1, 2]) as io::error is errors::unsupported); - io::close(&s); + io::close(&s)!; let in: [6]u8 = [0, 1, 2, 3, 4, 5]; let source = dynamic_from(in, io::mode::READ); @@ -242,7 +242,7 @@ fn copy(dest: *io::stream, src: *io::stream) (io::error | size) = { @test fn fixed() void = { let buf: [1024]u8 = [0...]; let stream = fixed(buf, io::mode::WRITE); - defer io::close(&stream); + defer io::close(&stream)!; let n = 0z; n += io::write(&stream, strings::toutf8("hello ")) as size; @@ -254,7 +254,7 @@ fn copy(dest: *io::stream, src: *io::stream) (io::error | size) = { let out: [2]u8 = [0...]; let s = fixed([1u8, 2u8], io::mode::READ); - defer io::close(&s); + defer io::close(&s)!; assert(io::read(&s, out[..1]) as size == 1 && out[0] == 1); assert(io::seek(&s, 1, io::whence::CUR) as io::off == 2: io::off); assert(io::read(&s, buf[..]) is io::EOF); diff --git a/cmd/hare/plan.ha b/cmd/hare/plan.ha @@ -239,9 +239,9 @@ fn execute( ) (void | exec::error | !exec::exit_status) = { if (verbose) { for (let i = 0z; i < len(task.cmd); i += 1) { - fmt::errorf("{} ", task.cmd[i])!; + fmt::errorf("{} ", task.cmd[i])?; }; - fmt::errorln()!; + fmt::errorln()?; }; let cmd = match (exec::cmd(task.cmd[0], task.cmd[1..]...)) { @@ -264,20 +264,20 @@ fn execute( let proc = exec::start(&cmd)?; if (pipe.0 != 0) { - io::close(pipe.1); + io::close(pipe.1)?; }; let cleared = false; if (pipe.0 != 0) { for (true) { let buf: [os::BUFSIZ]u8 = [0...]; - match (io::read(pipe.0, buf)!) { + match (io::read(pipe.0, buf)?) { case let n: size => if (!cleared) { progress_clear(plan); cleared = true; }; - io::write(os::stderr, buf[..n])!; + io::write(os::stderr, buf[..n])?; case io::EOF => break; }; diff --git a/cmd/hare/release.ha b/cmd/hare/release.ha @@ -108,7 +108,7 @@ fn do_release( defer os::rmdirall(dir)!; const changelog = temp::named(os::cwd, dir, io::mode::WRITE)?; const clfile = changelog.0, changelog = changelog.1; - defer io::close(clfile); + defer io::close(clfile)!; fmt::fprintfln(clfile, changelog_template, lasttag, name, newtag)?; shortlog(clfile, range)?; @@ -139,7 +139,7 @@ fn do_initial_release(ver: (modversion | increment)) (void | release_error) = { defer os::rmdirall(dir)!; const changelog = temp::named(os::cwd, dir, io::mode::WRITE)?; const clfile = changelog.0, changelog = changelog.1; - defer io::close(clfile); + defer io::close(clfile)!; fmt::fprintfln(clfile, initial_template, name, newtag)?; git_runcmd("tag", "-aeF", changelog, newtag)?; @@ -318,10 +318,10 @@ fn signtag(tmpdir: str, name: str, tag: str, key: str) (void | release_error) = const archive = exec::start(&archive)?; const ssh = exec::start(&ssh)?; const note = exec::start(&note)?; - io::close(pipe1.0); - io::close(pipe1.1); - io::close(pipe2.0); - io::close(pipe2.1); + io::close(pipe1.0)?; + io::close(pipe1.1)?; + io::close(pipe2.0)?; + io::close(pipe2.1)?; exec::check(&exec::wait(&archive)?)?; exec::check(&exec::wait(&ssh)?)?; exec::check(&exec::wait(&note)?)?; @@ -337,12 +337,12 @@ fn git_runcmd(args: str...) (void | release_error) = { fn git_readcmd(args: str...) (str | release_error) = { const pipe = exec::pipe(); - defer io::close(pipe.0); + defer io::close(pipe.0)!; const cmd = exec::cmd("git", args...)?; exec::addfile(&cmd, os::stdout_file, pipe.1); exec::addfile(&cmd, os::stderr, exec::nullfd); const proc = exec::start(&cmd)?; - io::close(pipe.1); + io::close(pipe.1)?; const result = io::drain(pipe.0)?; const status = exec::wait(&proc)?; exec::check(&status)?; diff --git a/cmd/harec/errors.ha b/cmd/harec/errors.ha @@ -22,7 +22,7 @@ fn printerr(err: parse::error) void = { fn printerr_syntax(err: lex::syntax) void = { let location = err.0, details = err.1; let file = os::open(location.path)!; - defer io::close(file); + defer io::close(file)!; let line = 1u; for (line < location.line) { diff --git a/cmd/harec/gen.ha b/cmd/harec/gen.ha @@ -29,7 +29,7 @@ fn gen(out: io::handle, store: *types::typestore, unit: *unit::unit) void = { }, ... }; - defer io::close(&ctx.buf); + defer io::close(&ctx.buf)!; for (let i = 0z; i < len(unit.decls); i += 1) { match (unit.decls[i].decl) { case let func: unit::decl_func => diff --git a/cmd/harec/main.ha b/cmd/harec/main.ha @@ -70,10 +70,10 @@ export fn main() void = { fmt::fatal("Error opening {}: {}", cmd.args[i], fs::strerror(err)); }; - defer io::close(input); + defer io::close(input)!; static let buf: [os::BUFSIZ]u8 = [0...]; let bufin = bufio::buffered(input, buf, []); - defer io::close(&bufin); + defer io::close(&bufin)!; let lexer = lex::init(&bufin, cmd.args[i]); let su = match (parse::subunit(&lexer)) { diff --git a/cmd/haredoc/docstr.ha b/cmd/haredoc/docstr.ha @@ -143,7 +143,7 @@ fn scanref(par: *parser) (token | void) = { }; const buf = strio::dynamic(); - defer io::close(&buf); + defer io::close(&buf)!; // TODO: Handle invalid syntax here for (true) { match (bufio::scanrune(&par.src)!) { diff --git a/cmd/haredoc/html.ha b/cmd/haredoc/html.ha @@ -52,17 +52,17 @@ fn html_escape(out: io::handle, in: str) (size | io::error) = { @test fn html_escape() void = { let sink = strio::dynamic(); - defer io::close(&sink); + defer io::close(&sink)!; html_escape(&sink, "hello world!")!; assert(strio::string(&sink) == "hello world!"); let sink = strio::dynamic(); - defer io::close(&sink); + defer io::close(&sink)!; html_escape(&sink, "\"hello world!\"")!; assert(strio::string(&sink) == "&quot;hello world!&quot;"); let sink = strio::dynamic(); - defer io::close(&sink); + defer io::close(&sink)!; html_escape(&sink, "<hello & 'world'!>")!; assert(strio::string(&sink) == "&lt;hello &amp; &apos;world&apos;!&gt;"); }; @@ -616,7 +616,7 @@ fn type_html( ) (size | io::error) = { if (brief) { let buf = strio::dynamic(); - defer io::close(&buf); + defer io::close(&buf)!; unparse::_type(&buf, indent, _type)?; return html_escape(out, strio::string(&buf))?; }; diff --git a/cmd/haredoc/main.ha b/cmd/haredoc/main.ha @@ -152,7 +152,7 @@ export fn main() void = { defer match (readme) { case void => void; case let f: io::file => - io::close(f); + io::close(f)!; }; if (decl != "") { @@ -198,7 +198,7 @@ export fn main() void = { fmt::fatal("Error: {}", strerror(err)); }; - io::close(ctx.out); + io::close(ctx.out)!; match (ctx.pager) { case void => void; case let proc: exec::process => @@ -323,7 +323,7 @@ fn scan(path: str) (ast::subunit | error) = { case let err: fs::error => fmt::fatal("Error reading {}: {}", path, fs::strerror(err)); }; - defer io::close(input); + defer io::close(input)!; const lexer = lex::init(input, path, lex::flags::COMMENTS); return parse::subunit(&lexer)?; }; diff --git a/cmd/haredoc/tty.ha b/cmd/haredoc/tty.ha @@ -200,13 +200,13 @@ fn prototype_tty( // lines const linelen = if (len(t.params) == 0) { let strm = strio::dynamic(); - defer io::close(&strm); + defer io::close(&strm)!; type_tty(&strm, indent, *t.result)?; retname = strings::dup(strio::string(&strm)); yield 0z; // only use one line if there's no parameters } else { let strm = strio::dynamic(); - defer io::close(&strm); + defer io::close(&strm)!; let linelen = indent * 8 + 5; linelen += if (len(t.params) != 0) len(t.params) * 3 - 1 else 0; for (let i = 0z; i < len(t.params); i += 1) { diff --git a/cmd/haretype/main.ha b/cmd/haretype/main.ha @@ -23,7 +23,7 @@ fn typeinfo( } else { const stream = bufio::fixed(strings::toutf8(s), io::mode::READ); const stream = &stream.stream; - defer io::close(stream); + defer io::close(stream)!; const lexer = lex::init(stream, "-"); const atype = parse::_type(&lexer)?; defer ast::type_finish(atype); diff --git a/crypto/blake2b/+test.ha b/crypto/blake2b/+test.ha @@ -29,7 +29,7 @@ use strio; }; hash::sum(&blake, sum); let out = strio::dynamic(); - defer io::close(&out); + defer io::close(&out)!; hex::encode(&out, sum)!; assert(strio::string(&out) == vectors[i].out); }; @@ -56,7 +56,7 @@ use strio; hash::sum(&blake, sum); let hex = strio::dynamic(); - defer io::close(&hex); + defer io::close(&hex)!; for (let j = 0z; j < len(sum); j += 1) { fmt::fprintf(&hex, "{:02x}", sum[j])!; diff --git a/crypto/blake2b/blake2b.ha b/crypto/blake2b/blake2b.ha @@ -178,7 +178,7 @@ fn mix(v: *[16]u64, a: size, b: size, c: size, d: size, x: u64, y: u64) void = { v[b] = math::rotr64(v[b] ^ v[c], R4); }; -fn close(stream: *io::stream) void = { +fn close(stream: *io::stream) (void | io::error) = { let s = stream: *digest; bytes::zero((s.h[..]: *[*]u8)[..len(s.h) * size(u32)]); bytes::zero(s.block); diff --git a/crypto/hmac/hmac.ha b/crypto/hmac/hmac.ha @@ -91,5 +91,5 @@ fn gensum(mac: *mac::mac, dest: []u8) void = { fn finish(mac: *mac::mac) void = { let hm = mac: *state; bytes::zero(hm.keypad); - io::close(hm.h); + io::close(hm.h)!; }; diff --git a/crypto/hmac/sha1.ha b/crypto/hmac/sha1.ha @@ -52,5 +52,5 @@ fn sha1sum(mac: *mac::mac, dest: []u8) void = { fn sha1finish(mac: *mac::mac) void = { let hm = mac: *sha1state; bytes::zero(hm.keypad); - io::close(&hm.h); + io::close(&hm.h)!; }; diff --git a/crypto/hmac/sha256.ha b/crypto/hmac/sha256.ha @@ -52,5 +52,5 @@ fn sha256sum(mac: *mac::mac, dest: []u8) void = { fn sha256finish(mac: *mac::mac) void = { let hm = mac: *sha256state; bytes::zero(hm.keypad); - io::close(&hm.h); + io::close(&hm.h)!; }; diff --git a/crypto/sha1/sha1.ha b/crypto/sha1/sha1.ha @@ -224,7 +224,7 @@ fn block(h: *state, p: []u8) void = { h.h[4] = h4; }; -fn close(stream: *io::stream) void = { +fn close(stream: *io::stream) (void | io::error) = { let s = stream: *state; bytes::zero((s.h[..]: *[*]u8)[..len(s.h) * size(u32)]); bytes::zero(s.x); diff --git a/crypto/sha256/sha256.ha b/crypto/sha256/sha256.ha @@ -213,7 +213,7 @@ fn block(h: *state, buf: []u8) void = { h.h[7] = h7; }; -fn close(stream: *io::stream) void = { +fn close(stream: *io::stream) (void | io::error) = { let s = stream: *state; bytes::zero((s.h[..]: *[*]u8)[..len(s.h) * size(u32)]); bytes::zero(s.x); diff --git a/crypto/sha512/sha512.ha b/crypto/sha512/sha512.ha @@ -340,7 +340,7 @@ fn block(h: *digest, p: []u8) void = { h.h[7] = h7; }; -fn close(stream: *io::stream) void = { +fn close(stream: *io::stream) (void | io::error) = { let s = stream: *digest; bytes::zero((s.h[..]: *[*]u8)[..len(s.h) * size(u32)]); bytes::zero(s.x); diff --git a/encoding/base32/base32.ha b/encoding/base32/base32.ha @@ -134,7 +134,7 @@ fn encode_writer( return l; }; -fn encode_closer(s: *io::stream) void = { +fn encode_closer(s: *io::stream) (void | io::error) = { let s = s: *encoder; if (s.avail == 0) { return; @@ -169,7 +169,7 @@ export fn encodeslice(enc: *encoding, in: []u8) []u8 = { let out = bufio::dynamic(io::mode::WRITE); let encoder = new_encoder(enc, &out); io::write(&encoder, in)!; - io::close(&encoder); + io::close(&encoder)!; return bufio::buffer(&out); }; @@ -204,7 +204,7 @@ export fn encodestr(enc: *encoding, in: []u8) str = { let out = bufio::dynamic(io::mode::RDWR); let enc = new_encoder(&std_encoding, &out); io::write(&enc, in[..i]) as size; - io::close(&enc); + io::close(&enc)!; let outb = bufio::buffer(&out); assert(bytes::equal(outb, strings::toutf8(expect[i]))); free(outb); @@ -216,7 +216,7 @@ export fn encodestr(enc: *encoding, in: []u8) str = { out = bufio::dynamic(io::mode::RDWR); enc = new_encoder(&hex_encoding, &out); io::write(&enc, in[..i]) as size; - io::close(&enc); + io::close(&enc)!; let outb = bufio::buffer(&out); assert(bytes::equal(outb, strings::toutf8(expect_hex[i]))); free(outb); @@ -366,7 +366,7 @@ export fn decodeslice( let out = bufio::dynamic(io::mode::WRITE); match (io::copy(&out, &decoder)) { case io::error => - io::close(&out); + io::close(&out)!; return errors::invalid; case size => return bufio::buffer(&out); diff --git a/encoding/base64/base64.ha b/encoding/base64/base64.ha @@ -135,7 +135,7 @@ fn encode_writer( return l; }; -fn encode_closer(s: *io::stream) void = { +fn encode_closer(s: *io::stream) (void | io::error) = { let s = s: *encoder; if (s.avail == 0) { return; @@ -157,7 +157,7 @@ fn encode_closer(s: *io::stream) void = { for (let i = 0z; i < np; i += 1) { encb[3 - i] = PADDING; }; - io::write(s.out, encb)!; // TODO https://todo.sr.ht/~sircmpwn/hare/568 + io::write(s.out, encb)?; }; // Encodes a byte slice in base 64, using the given encoding, returning a slice @@ -166,7 +166,7 @@ export fn encodeslice(enc: *encoding, in: []u8) []u8 = { let out = bufio::dynamic(io::mode::WRITE); let encoder = newencoder(enc, &out); io::writeall(&encoder, in)!; - io::close(&encoder); + io::close(&encoder)!; return bufio::buffer(&out); }; @@ -178,8 +178,14 @@ export fn encode( buf: []u8, ) (size | io::error) = { const enc = newencoder(enc, out); - defer io::close(&enc); - return io::writeall(&enc, buf)?; + match (io::writeall(&enc, buf)) { + case let z: size => + io::close(&enc)?; + return z; + case let err: io::error => + io::close(&enc): void; + return err; + }; }; // Encodes a byte slice in base 64, using the given encoding, returning a @@ -204,7 +210,7 @@ export fn encodestr(enc: *encoding, in: []u8) str = { let out = bufio::dynamic(io::mode::WRITE); let encoder = newencoder(&std_encoding, &out); io::writeall(&encoder, in[..i])!; - io::close(&encoder); + io::close(&encoder)!; let encb = bufio::buffer(&out); defer free(encb); assert(bytes::equal(encb, strings::toutf8(expect[i]))); @@ -353,7 +359,7 @@ export fn decodeslice( let out = bufio::dynamic(io::mode::WRITE); match (io::copy(&out, &decoder)) { case io::error => - io::close(&out); + io::close(&out)!; return errors::invalid; case size => return bufio::buffer(&out); @@ -374,7 +380,14 @@ export fn decode( buf: []u8, ) (size | io::EOF | io::error) = { const enc = newdecoder(enc, in); - return io::readall(&enc, buf)?; + match (io::readall(&enc, buf)) { + case let ret: (size | io::EOF) => + io::close(&enc)?; + return ret; + case let err: io::error => + io::close(&enc): void; + return err; + }; }; @test fn decode() void = { diff --git a/encoding/hex/hex.ha b/encoding/hex/hex.ha @@ -117,7 +117,7 @@ export fn dump(out: io::handle, data: []u8) (void | io::error) = { ]; let sink = strio::dynamic(); - defer io::close(&sink); + defer io::close(&sink)!; dump(&sink, in) as void; let s = strio::string(&sink); diff --git a/fs/fs.ha b/fs/fs.ha @@ -116,15 +116,23 @@ export fn move(fs: *fs, oldpath: str, newpath: str) (void | error) = { let st = stat(fs, oldpath)?; assert(isfile(st.mode), "TODO: move non-regular files"); let old = open(fs, oldpath)?; - defer io::close(old); - let new = create(fs, newpath, st.mode)?; - defer io::close(new); + let new = match (create(fs, newpath, st.mode)) { + case let h: io::handle => + yield h; + case let err: error => + io::close(old): void; + return err; + }; match (io::copy(new, old)) { case let err: io::error => + io::close(new): void; + io::close(old): void; remove(fs, newpath)?; return err; case size => void; }; + io::close(new)?; + io::close(old)?; remove(fs, oldpath)?; }; diff --git a/glob/glob.ha b/glob/glob.ha @@ -65,7 +65,7 @@ export fn glob(pattern: str, flags: flags...) generator = { // Frees all memory allocated by the generator. export fn finish(gen: *generator) void = { strstack_free(&gen.pats); - io::close(&gen.tmps); + io::close(&gen.tmps)!; }; // Returns a generated pathname. The returned string is valid until [[next]] @@ -227,7 +227,7 @@ fn strstack_init() strstack = strstack { fn strstack_free(ss: *strstack) void = { for (let i = 0z; i < len(ss.bufv); i += 1) { - io::close(&ss.bufv[i]); + io::close(&ss.bufv[i])!; }; free(ss.bufv); }; diff --git a/hare/lex/lex.ha b/hare/lex/lex.ha @@ -358,7 +358,7 @@ fn lex_comment(lexr: *lexer) (void | error) = { }; let buf = strio::dynamic(); - defer io::close(&buf); + defer io::close(&buf)!; for (true) match (next(lexr)?) { case io::EOF => break; diff --git a/hare/module/manifest.ha b/hare/module/manifest.ha @@ -66,16 +66,18 @@ export fn manifest_load(ctx: *context, ident: ast::ident) (manifest | error) = { case let file: io::handle => yield file; }; - defer io::close(file); let inputs: []input = [], versions: []version = []; let buf: [4096]u8 = [0...]; let file = bufio::buffered(file, buf, []); for (true) { - let line = match (bufio::scanline(&file)?) { + let line = match (bufio::scanline(&file)) { case io::EOF => break; + case let err: io::error => + io::close(&file): void; + return err; case let line: []u8 => yield line; }; @@ -236,6 +238,7 @@ export fn manifest_load(ctx: *context, ident: ast::ident) (manifest | error) = { }; }; + io::close(&file)?; manifest.inputs = inputs; manifest.versions = versions; return manifest; @@ -293,7 +296,7 @@ export fn manifest_write(ctx: *context, man: *manifest) (void | error) = { let name = file.1, file = file.0; defer { fs::remove(ctx.fs, name): void; - io::close(file); + io::close(file)!; }; let ident = unparse::identstr(man.ident); diff --git a/hare/module/scan.ha b/hare/module/scan.ha @@ -350,7 +350,6 @@ fn scan_file( deps: *[]ast::ident, ) ([]u8 | error) = { let f = fs::open(ctx.fs, path)?; - defer io::close(f); let sha = sha256::sha256(); hash::write(&sha, strings::toutf8(path)); hash::write(&sha, [ABI_VERSION]); @@ -358,17 +357,34 @@ fn scan_file( if (ftype == filetype::HARE) { let tee = io::tee(f, &sha); let lexer = lex::init(&tee, path); - let imports = parse::imports(&lexer)?; + let imports = match (parse::imports(&lexer)) { + case let im: []ast::import => + yield im; + case let err: parse::error => + io::close(f): void; + return err; + }; for (let i = 0z; i < len(imports); i += 1) { if (!have_ident(deps, imports[i].ident)) { append(deps, imports[i].ident); }; }; // Finish spooling out the file for the SHA - io::copy(io::empty, &tee)?; + match (io::copy(io::empty, &tee)) { + case size => void; + case let err: io::error => + io::close(f): void; + return err; + }; } else { - io::copy(&sha, f)?; + match (io::copy(&sha, f)) { + case size => void; + case let err: io::error => + io::close(f): void; + return err; + }; }; + io::close(f)?; let tmp: [sha256::SIZE]u8 = [0...]; hash::sum(&sha, tmp); diff --git a/hare/parse/+test/roundtrip.ha b/hare/parse/+test/roundtrip.ha @@ -28,7 +28,7 @@ fn roundtrip(src: str) void = { }; defer ast::subunit_finish(u); let out = strio::dynamic(); - defer io::close(&out); + defer io::close(&out)!; let z = unparse::subunit(&out, u) as size; let unsrc = strio::string(&out); assert(z == len(unsrc)); diff --git a/hare/parse/parse.ha b/hare/parse/parse.ha @@ -38,7 +38,7 @@ fn want(lexer: *lex::lexer, want: lex::ltok...) (lex::token | error) = { }; let buf = strio::dynamic(); - defer io::close(&buf); + defer io::close(&buf)!; for (let i = 0z; i < len(want); i += 1) { const tstr = if (want[i] == ltok::NAME) "name" else lex::tokstr((want[i], void, lex::mkloc(lexer))); diff --git a/hare/unparse/type.ha b/hare/unparse/type.ha @@ -80,13 +80,13 @@ export fn prototype( // lines const linelen = if (len(t.params) == 0) { let strm = strio::dynamic(); - defer io::close(&strm); + defer io::close(&strm)!; _type(&strm, indent, *t.result)?; retname = strings::dup(strio::string(&strm)); yield 0z; // only use one line if there's no parameters } else { let strm = strio::dynamic(); - defer io::close(&strm); + defer io::close(&strm)!; let linelen = indent * 8 + 5; linelen += if (len(t.params) != 0) len(t.params) * 3 - 1 else 0; for (let i = 0z; i < len(t.params); i += 1) { diff --git a/hash/hash.ha b/hash/hash.ha @@ -31,7 +31,7 @@ export fn write(h: *hash, buf: const []u8) size = io::write(h, buf) as size; // Closes a hash, freeing its resources and discarding the checksum. -export fn close(h: *hash) void = io::close(h); +export fn close(h: *hash) void = io::close(h)!; // Populates the user-provided buffer with the current sum. export fn sum(h: *hash, buf: []u8) void = { diff --git a/io/handle.ha b/io/handle.ha @@ -38,12 +38,12 @@ export fn write(h: handle, buf: const []u8) (size | error) = { // Closes a [[handle]]. No further operations against this handle are permitted // after calling this function. -export fn close(h: handle) void = { +export fn close(h: handle) (void | error) = { match (h) { case let fd: file => fd_close(fd); case let st: *stream => - st_close(st); + st_close(st)?; }; }; diff --git a/io/stream.ha b/io/stream.ha @@ -61,11 +61,11 @@ fn st_write(s: *stream, buf: const []u8) (size | error) = { }; }; -fn st_close(s: *stream) void = { +fn st_close(s: *stream) (void | error) = { match (s.closer) { case null => void; case let c: *closer => - c(s); + c(s)?; }; }; diff --git a/io/types.ha b/io/types.ha @@ -51,7 +51,7 @@ export type writer = fn(s: *stream, buf: const []u8) (size | error); // The interface for a stream which can be closed. This function should close // and free any underlying resources, and cannot be used again. -export type closer = fn(s: *stream) void; +export type closer = fn(s: *stream) (void | error); // The interface for a stream which has first-class support for copying data // from another stream. Often this only works if the second stream is of the diff --git a/linux/timerfd/timerfd.ha b/linux/timerfd/timerfd.ha @@ -127,5 +127,5 @@ export fn read( // reading here would block us forever unset(blocking_fd)!; - io::close(blocking_fd); + io::close(blocking_fd)!; }; diff --git a/mime/system.ha b/mime/system.ha @@ -19,19 +19,23 @@ export def SYSTEM_DB: str = "/etc/mime.types"; fn load_systemdb() (void | fs::error | io::error) = { const file = os::open(SYSTEM_DB)?; - defer io::close(file); let buf: [os::BUFSIZ]u8 = [0...]; - const file = bufio::buffered(file, buf, []); - defer io::close(&file); + const strm = bufio::buffered(file, buf, []); for (true) { - const line = match (bufio::scanline(&file)?) { + const line = match (bufio::scanline(&strm)?) { case let bytes: []u8 => yield match (strings::try_fromutf8(bytes)) { case utf8::invalid => fmt::errorln("Warning: /etc/mime.types contains invalid UTF-8")!; + io::close(&strm)?; + io::close(file)?; return; + case let err: io::error => + io::close(&strm): void; + io::close(file): void; + return err; case let s: str => yield s; }; @@ -68,6 +72,9 @@ fn load_systemdb() (void | fs::error | io::error) = { }; register_heap(entry); }; + + io::close(&strm)?; + io::close(file)?; }; fn register_heap(mime: *mimetype...) void = { diff --git a/net/dns/query.ha b/net/dns/query.ha @@ -26,9 +26,9 @@ export fn query(query: *message, servers: ip::addr...) (*message | error) = { }; let socket4 = udp::listen(ip::ANY_V4, 0)?; - defer io::close(socket4); + defer io::close(socket4)!; let socket6 = udp::listen(ip::ANY_V6, 0)?; - defer io::close(socket6); + defer io::close(socket6)!; const pollfd: [_]poll::pollfd = [ poll::pollfd { fd = socket4, diff --git a/net/uri/parse.ha b/net/uri/parse.ha @@ -125,7 +125,7 @@ fn parse_scheme(in: *strings::iterator) (str | invalid) = { fn parse_authority(in: *strings::iterator) ((str, u16, str) | invalid) = { // Scan everything until '@' or ':' or '/', then decide what it is let buf = strio::dynamic(); - defer io::close(&buf); + defer io::close(&buf)!; let host = ""; let port = 0u16; let userinfo = ""; @@ -200,7 +200,7 @@ type path_mode = enum { fn parse_path(in: *strings::iterator, mode: path_mode) (str | invalid) = { let buf = strio::dynamic(); - defer io::close(&buf); + defer io::close(&buf)!; // With rootless path, we need at least one segment if (mode == path_mode::ROOTLESS) { @@ -272,7 +272,7 @@ fn parse_query(in: *strings::iterator) (str | invalid) = { fn parse_fragment(in: *strings::iterator) (str | invalid) = { let buf = strio::dynamic(); - defer io::close(&buf); + defer io::close(&buf)!; for (true) { match (strings::next(in)) { @@ -291,7 +291,7 @@ fn parse_fragment(in: *strings::iterator) (str | invalid) = { fn parse_port(in: *strings::iterator) (u16 | invalid) = { let buf = strio::dynamic(); - defer io::close(&buf); + defer io::close(&buf)!; for (true) { const r = match (strings::next(in)) { case let r: rune => @@ -328,7 +328,7 @@ fn percent_decode(s: str) (str | invalid) = { fn percent_decode_static(out: io::handle, s: str) (void | invalid) = { let iter = strings::iter(s); let tmp = strio::dynamic(); - defer io::close(&tmp); + defer io::close(&tmp)!; for (true) { match (strings::next(&iter)) { case let r: rune => diff --git a/net/uri/query.ha b/net/uri/query.ha @@ -16,8 +16,8 @@ export fn decodequery(q: const str) query_decoder = query_decoder { // Frees resources associated with the [[query_decoder]]. export fn query_finish(dec: *query_decoder) void = { - io::close(&dec.bufs.0); - io::close(&dec.bufs.1); + io::close(&dec.bufs.0)!; + io::close(&dec.bufs.1)!; }; // Retrieves the next (key, value) pair from the query. The return value is diff --git a/os/+freebsd/stdfd.ha b/os/+freebsd/stdfd.ha @@ -44,5 +44,5 @@ export def BUFSIZ: size = 4096; // 4 KiB @fini fn fini_stdfd() void = { // Flush any pending writes - io::close(stdout); + io::close(stdout)!; }; diff --git a/os/+linux/stdfd.ha b/os/+linux/stdfd.ha @@ -44,5 +44,5 @@ export def BUFSIZ: size = 4096; // 4 KiB @fini fn fini_stdfd() void = { // Flush any pending writes - io::close(stdout); + io::close(stdout)!; }; diff --git a/os/exec/exec+freebsd.ha b/os/exec/exec+freebsd.ha @@ -36,10 +36,10 @@ export fn fork() (int | void | error) = { // let pipe = exec::pipe(); // exec::addfile(&cmd, pipe.1, os::stdout_file); // let proc = exec::start(&cmd)!; -// io::close(pipe.1); +// io::close(pipe.1)!; // // let data = io::drain(pipe.0)!; -// io::close(pipe.0); +// io::close(pipe.0)!; // exec::wait(&proc)!; export fn pipe() (io::file, io::file) = { return unix::pipe()!; @@ -115,7 +115,7 @@ fn platform_exec(cmd: *command) error = { case nullfd => yield devnull; case closefd => - io::close(cmd.files[i].1); + io::close(cmd.files[i].1)?; continue; }; diff --git a/os/exec/exec+linux.ha b/os/exec/exec+linux.ha @@ -36,10 +36,10 @@ export fn fork() (int | void | error) = { // let pipe = exec::pipe(); // exec::addfile(&cmd, pipe.1, os::stdout_file); // let proc = exec::start(&cmd)!; -// io::close(pipe.1); +// io::close(pipe.1)!; // // let data = io::drain(pipe.0)!; -// io::close(pipe.0); +// io::close(pipe.0)!; // exec::wait(&proc)!; export fn pipe() (io::file, io::file) = { return unix::pipe()!; @@ -116,7 +116,7 @@ fn platform_exec(cmd: *command) error = { case nullfd => yield devnull; case closefd => - io::close(cmd.files[i].1); + io::close(cmd.files[i].1)?; continue; }; diff --git a/os/exec/types.ha b/os/exec/types.ha @@ -22,7 +22,7 @@ export type command = struct { export type nocmd = !void; // All errors that can be returned from os::exec. -export type error = !(nocmd | ...errors::error); +export type error = !(nocmd | ...errors::error | io::error); // Returns a human-readable message for the given error. export fn strerror(err: error) const str = { @@ -31,6 +31,8 @@ export fn strerror(err: error) const str = { return "Command not found"; case let err: errors::opaque => return errors::strerror(err); + case let err: io::error => + return io::strerror(err); }; }; diff --git a/strio/ops.ha b/strio/ops.ha @@ -19,7 +19,7 @@ export fn concat(out: io::handle, strs: str...) (size | io::error) = { @test fn concat() void = { let st = dynamic(); - defer io::close(&st); + defer io::close(&st)!; concat(&st, "hello") as size; concat(&st, " ", "world") as size; assert(string(&st) == "hello world"); @@ -42,7 +42,7 @@ export fn join(out: io::handle, delim: str, strs: str...) (size | io::error) = { @test fn join() void = { let st = dynamic(); - defer io::close(&st); + defer io::close(&st)!; join(&st, "::", "hello", "world") as size; assert(string(&st) == "hello::world"); truncate(&st); @@ -70,7 +70,7 @@ export fn rjoin(out: io::handle, delim: str, strs: str...) (size | io::error) = @test fn rjoin() void = { let st = dynamic(); - defer io::close(&st); + defer io::close(&st)!; rjoin(&st, "::", "hello", "world") as size; assert(string(&st) == "world::hello"); truncate(&st); diff --git a/strio/stream.ha b/strio/stream.ha @@ -87,14 +87,14 @@ fn dynamic_write(s: *io::stream, buf: const []u8) (size | io::error) = { return len(buf); }; -fn dynamic_close(s: *io::stream) void = { +fn dynamic_close(s: *io::stream) (void | io::error) = { const s = s: *stream; free(s.buf); }; @test fn dynamic() void = { let stream = dynamic(); - defer io::close(&stream); + defer io::close(&stream)!; io::write(&stream, strings::toutf8("hello ")) as size; io::write(&stream, strings::toutf8("world")) as size; assert(string(&stream) == "hello world"); diff --git a/time/chrono/timezone.ha b/time/chrono/timezone.ha @@ -180,7 +180,7 @@ export const LOCAL: locality = &TZ_local; case => return; }; - defer io::close(file); + defer io::close(file)!; static let buf: [os::BUFSIZ]u8 = [0...]; const file = bufio::buffered(file, buf, []); diff --git a/time/chrono/tzdb.ha b/time/chrono/tzdb.ha @@ -34,18 +34,28 @@ export fn tz(name: str) (timezone | fs::error | io::error | invalidtzif) = { path::add(&filepath, ZONEINFO_PREFIX, name)!; const fpath = path::string(&filepath); const file = os::open(fpath)?; - defer io::close(file); static let buf: [os::BUFSIZ]u8 = [0...]; - const file = bufio::buffered(file, buf, []); - const tz = parse_tzif(&file, timezone { + const bufstrm = bufio::buffered(file, buf, []); + match (parse_tzif(&bufstrm, timezone { name = name, timescale = &utc, daylength = EARTH_DAY, ... - })?; - - return tz; + })) { + case let tz: timezone => + io::close(&bufstrm)?; + io::close(file)?; + return tz; + case invalidtzif => + io::close(&bufstrm): void; + io::close(file): void; + return invalidtzif; + case let err: io::error => + io::close(&bufstrm): void; + io::close(file): void; + return err; + }; }; // Parses data in the TZif format, and returns the given timezone with the diff --git a/unix/hosts/lookup.ha b/unix/hosts/lookup.ha @@ -14,7 +14,7 @@ export fn lookup(name: str) []ip::addr = { // XXX: Would be cool if we could do this without allocating anything // XXX: Would be cool to have meaningful error handling(?) const file = os::open(PATH)!; - defer io::close(file); + defer io::close(file)!; let addrs: []ip::addr = []; for (true) { diff --git a/unix/passwd/group.ha b/unix/passwd/group.ha @@ -76,7 +76,7 @@ export fn getgroup(name: str) (grent | void) = { case => abort("Unable to open /etc/group"); }; - defer io::close(file); + defer io::close(file)!; for (true) { let ent = match (nextgr(file)) { diff --git a/unix/passwd/passwd.ha b/unix/passwd/passwd.ha @@ -95,7 +95,7 @@ export fn getuser(username: str) (pwent | void) = { case => abort("Can't open /etc/passwd"); }; - defer io::close(file); + defer io::close(file)!; for (true) { let ent = match (nextpw(file)) { diff --git a/unix/resolvconf/load.ha b/unix/resolvconf/load.ha @@ -24,7 +24,7 @@ export fn load() []ip::addr = { }; const file = os::open(PATH)!; - defer io::close(file); + defer io::close(file)!; for (true) { const line = match (bufio::scanline(file)) {