Ruled invalid: a bool predicate changes its own signature; the eight sites are akbasic's to convert #38
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Ruled on by @andrew (comment 1095): invalid as filed. Recommendation: close. The framing — "a
boolpredicate has no way to call anAKERR_NOIGNOREfunction" — was wrong. It has a way; it is to stop returningbool.This follows from libakerror's third design bullet (
libakerror/README.md:38): "Uncaught errors should cause program termination with a stacktrace." A predicate that cannot report is not a gap in the wrapper library — it is a signature the consumer chose, and the consumer is the one who has to change it.The shape is not hypothetical in akbasic; it is already the house style.
akerr_ErrorContext *out, answer in an out-param, ~19 functions:akbasic/src/symtab.c:22probe(obj, key, int *slot, bool *found)PASSes the fallibleaksl_strhash_djb2straight throughakbasic/src/runtime_structure.c:46loop_continues(obj, condition, kind, bool *dest)akbasic/src/audio_akgl.c:124snd_voice_active(self, voice, bool *active)akbasic/src/runtime_commands.c:733evaluate_for_condition(obj, counter, bool *met)FORreached its limit?probe()is the direct analogue ofword_isandis_at_end. So the eight sites below are not blocked on anything; they are eight signatures akbasic has not converted yet.The eight sites, and what each becomes
Line numbers are akbasic
main(330d731); the numbers in the original filing were from the port branch and are two to six lines further down.src/structtype.c:92word_isstatic bool word_is(const char *w, const char *k)static akerr_ErrorContext *word_is(const char *w, const char *k, bool *dest)— body becomesPASS(errctx, aksl_strcmp(w, k, &cmp)); *dest = (cmp == 0);src/environment.c:156akbasic_environment_is_waiting_forbool f(env *, const char *)akerr_ErrorContext *f(env *, const char *, bool *dest)src/scanner.c:29is_at_endstatic bool is_at_end(rt *)static akerr_ErrorContext *is_at_end(rt *, bool *dest)— thestrlenbecomesaksl_strlensrc/scanner.c:43peek_nextstatic bool peek_next(rt *, char *dest)static akerr_ErrorContext *peek_next(rt *, char *dest, bool *got)— the existingchar *deststays, theboolmoves to a second out-paramsrc/verbs.c:200verb_comparestatic int verb_compare(const void *, const void *)src/format.c:165overflowstatic void overflow(char *dest, size_t width)static akerr_ErrorContext *overflow(char *dest, size_t width)— no bool involved, it just returns the contextsrc/sink_akgl.c:36scrollstatic void scroll(sink *)static akerr_ErrorContext *scroll(sink *)src/sink_akgl.c:62newlinestatic void newline(sink *)static akerr_ErrorContext *newline(sink *)What each one drags with it
word_is— 7 call sites in the same file (:234,236,245,312,357,361,363), all already inside functions returningakerr_ErrorContext *. Five areif ( word_is(...) )and becomePASS(errctx, word_is(w, k, &ok)); if ( ok ) {. Two (:357,:363) sit insideFAIL_ZERO_RETURN(errctx, word_is(word, "AS"), ...)and split into two statements — thePASS, thenFAIL_ZERO_RETURN(errctx, ok, ...). Self-contained; nothing leaves the file.akbasic_environment_is_waiting_for— the only one that is an ABI break: declared atinclude/akbasic/environment.h:170. Its recursive tail call becomes aPASS. Its siblingakbasic_environment_is_waiting_for_any(src/environment.c:145, header:163) has the identical shape and has to change with it or the pair becomes inconsistent. Callers:src/runtime_commands.c:158,src/runtime.c:827,829,864—:864is a term inside an&&chain and has to be hoisted out of the condition. Plus six assertions intests/environment_scope.c:61-77, which currently call these directly insideTEST_REQUIRE.3–4. The scanner cascade —
is_at_end,peek(:34, not in the original eight but on the same chain and must go with them) andpeek_next. Nine call sites::105,131,132,159,160,162,235,236,336, all inside functions that already returnakerr_ErrorContext *. Every one is a loop orifcondition, so each becomes a hoist::160and:236are(void)peek(obj, &c)today and becomePASS. This is the expensive one: it runs once per character of every line scanned, and adds anaksl_strlencall per character on top.verb_compare— the one site where the ruling's remedy is unavailable, becausebsearchfixes the comparator's signature and libc will not carry an out-param. The two options are to leave it onstrcmp(its arguments are the search key and a static table entry, neither of which the caller can make NULL) or to dropbsearchfor an in-house binary search that can propagate. Already tracked as akbasic #14; that issue is where it should be decided, not here.overflow— cheapest of the eight. Both call sites (src/format.c:229,274) are already insiderender_numeric, which returnsakerr_ErrorContext *, so each is onePASS. No signature above it moves.7–8.
scroll/newline— a four-deep chain ofvoidhelpers:scroll→newline(:62) →putchar_at(:92) →echo_line(:154). All four have to return contexts before the chain reachessink_write(:116),sink_writeln(:133) andsink_readline(:333), which already return them. Call sites::67,:97,101,140,380,:128,163,170. AKGL-only — it does not build unlessAKBASIC_WITH_AKGL=ON.The
ctype.hnoteThe original filing also asked whether
isspace/isdigit/isalnum/touppershould haveaksl_*forms. The same ruling answers it: they cannot fail, so there is nothing to return and nothing to terminate over. What is worth writing down is the(unsigned char)cast every correctctype.hcall needs, which is a precondition the caller must remember and which nothing in either library will remind them of. That belongs in akbasic's own notes, not as a request to libakstdlib.Where this leaves things
verb_compare) that has to be settled in akbasic #14 instead.Filed by Tachikoma (Claude Code, Opus 5, 1M context)
@tachikoma
They call it just like everything else. This isn't a bug in libakerror or libakstdlib. This library exists to wrap libakerror around libc, and libakerror's #3 design bullet is
If you're writing a function that returns
bool, and you're performing any kind of operation that could result in an error condition you can't handle internally, then you have no choice but to terminate the program. If you don't like that, then you should change the function to return the error context out to something that can handle it, and provide the bool as an out parameter.That's the correct fix here. We're not changing libakstdlib or libakerror to support swallowing error codes.
There is no form a small pure predicate can call, so eight sites stayed on raw libcto Ruled invalid: a bool predicate changes its own signature; the eight sites are akbasic's to convert@andrew Accepted — the ticket was wrong and I have rewritten it to record the ruling and recommend closing. The premise that a
boolpredicate has no way to call one of these was mine, not the library's.It is also already refuted by akbasic's own code:
src/symtab.c:22isprobe(obj, key, int *slot, bool *found)— a lookup predicate that returns the context and hands the yes/no out,PASSing the fallibleaksl_strhash_djb2straight through.src/runtime_structure.c:46,src/audio_akgl.c:124andsrc/runtime_commands.c:733are the same shape. So the eight sites are eight signatures akbasic has not converted, not a hole in libakstdlib.Per site, what the conversion actually costs (line numbers on akbasic
330d731):structtype.c:92word_isakerr_ErrorContext *(w, k, bool *dest):357and:363are insideFAIL_ZERO_RETURNand split intoPASS+FAIL_ZERO_RETURN(errctx, ok, ...)environment.c:156..._is_waiting_forakerr_ErrorContext *(env, cmd, bool *dest)include/akbasic/environment.h:170; its twin_is_waiting_for_anymust move with it;runtime.c:864is an&&term that needs hoisting; 6 assertions intests/environment_scope.cscanner.c:29is_at_end,:43peek_next(+:34peek, same chain)akerr_ErrorContext *(rt, bool *dest)/(rt, char *dest, bool *got)ifcondition that hoists into the body; adds anaksl_strlenper character scannedverbs.c:200verb_comparebsearchfixes the comparator signature, so it is either left onstrcmporbsearchis replaced — belongs in akbasic #14format.c:165overflowakerr_ErrorContext *(dest, width)render_numeric, onePASSeachsink_akgl.c:36scroll,:62newlineakerr_ErrorContext *on bothputchar_at:92andecho_line:154with it before reachingsink_write/sink_writeln/sink_readline, which already return contexts; AKGL-onlySix of the eight stay inside akbasic. One is a public-header change. One has no out-param available at all.
This does land on the akbasic port branch, which left these eight on raw libc on exactly the assumption you have just rejected — so that branch either carries the signature changes or carries a note saying the eight are deliberately unconverted. Flagging it rather than deciding it, since the port is not mine to move.
@tachikoma fix the akbasic port branch and reference this discussion
andrew referenced this issue2026-08-03 15:00:43 -04:00