Skip to content

self-host parity: higher-order functions, doc continuation, discharge fence #436

Description

@jasisz

Rewritten 2026-08-11. Part 2 of the original issue is fixed and has been removed — leaving it would send whoever picks this up chasing a module-resolution bug that no longer exists. The line anchors in part 1 have been refreshed, and the two divergences that are actually left have been added. See the comment below for the sweep this is based on.

A VM-vs-self-host corpus audit during 0.24 prep found the self-hosted interpreter (self_hosted/*.av, compiled to Rust under src/self_host/) lags the other backends on several language features. 0.24 "Divide" closed the tractable ones. This issue tracks what is left.

Regen after editing self_hosted/*.av: aver compile self_hosted/main.av --target rust --output self_hosted/out --module-root self_hosted --with-self-host-support --guest-entry runGuestCliProgram --with-replay --policy runtime, then copy self_hosted/out/srcsrc/self_host/ and cargo build.

1. Higher-order functions (Fn(...) parameters)

Still reproduces exactly as filed. The host prints 12; --self-host fails at parse with Expected ',' or ')' in parameter list.

fn dbl(x: Int) -> Int
    x * 2
fn applyTwice(f: Fn(Int) -> Int, x: Int) -> Int
    f(f(x))

Two layers, both unchanged:

  • Parser: skipTypeExpr in self_hosted/domain/parsermatch.av:224 handles TkIdent and TkLParen but has no TkFn case, so a Fn(A) -> B parameter type never parses. Add a TkFn branch that skips Fn ( types ) -> ret. (TkFn/TkArrow tokens already exist.)
  • Eval: ValFnRef is created at self_hosted/domain/eval/common.av:23 when an identifier resolves to a fn, but never consumed. callWithArgs / callWithArgsNormal (self_hosted/domain/eval/core.av:387 / :394) resolve the callee only as a top-level fn or a builtin and do not receive env, so a call f(x) where f is a local bound to ValFnRef falls through to callBuiltin("f", …) and fails. Both the named-env path (evalCall, core.av:608) and the slot-env path (evalCallSlot, core.av:1047) would need it, and the resolver may also reject a param as a call target.

The self-host is slot-resolved and was not designed for first-class fn values; this is a feature add, not a patch.

Worth weighing before anyone starts. Exactly one file in the 112-file example corpus uses an Fn(...) parameter — examples/formal/oracle_independent_products.av:11 — and it has no main, so it is not even in the parity sweep. examples/core/lambda.av does run under --self-host, but it is a lambda-calculus interpreter over a type Term and exercises no Aver first-class functions at all. So this is a real gap with almost no corpus pressure behind it.

2. Multi-line ? documentation continuation

examples/refinement/natural_app.av fails under --self-host with Expected expression, got Indent. Bisected to a ? doc string followed by an indented continuation line, the shape fn add uses in examples/refinement/natural/natural.av:

fn add(a: Nat, b: Nat) -> Nat
    ? "line one"
      "line two"

exposes [f], exposes opaque [Nat] and verify ... law ... given all parse fine — it is only the continuation. Small and self-contained, and fixing it takes natural_app.av to parity.

3. Literal smart-constructor discharge is fenced off

examples/data/json.av diverges because the self-host refuses 0.28's literal smart-constructor discharge, loudly and deliberately — reject_literal_refinement_discharge at src/main/commands.rs:1292, with the reasoning at :1277-1291. Fail-closed with a message, not a silent miscompile, so this is correct behaviour rather than a bug.

It does raise a standing question this issue should carry: whether the self-host is meant to track host typing rules as they land. The note at commands.rs:1287 puts the cost plainly — mirroring the discharge "is not the three syntactic predicates the literal-divisor rule needed; it is the whole recognizer". That is a tax on every future discharge rule, and it should be decided rather than absorbed.

Verification

Corpus parity sweep over every examples/**/*.av with a fn main, host versus --self-host, diffing stdout:

  • 43 exercised, 37 exact match
  • of the 6 differing: date.av is clock nondeterminism, and app.av / app_dot.av / pricing_app.av differ only by a host-side stderr warning about unchecked dependency-module verify blocks — their stdout is byte-identical
  • leaving 2 genuine: natural_app.av (section 2) and json.av (section 3)

Regen gate: AVER_SELF_HOST_REGEN=1 cargo test --test rust_self_host_regen -- --ignored.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions