ADR-0012: Surface Direct Functions for T2B

Status: accepted
Date: 2026-07-25

Context

T2A proves a narrow Surface-to-Core boundary for scalar assignments and branches, but it deliberately rejects functions and calls. The Surface AST has optional parameter and return annotations, while the current parser leaves them empty and the bridge typechecker assigns function parameters and results the dynamic Any type.

Core-N0 already has typed direct calls and tail calls. Reusing Any, guessing types from use sites, or treating captured bridge scope as a Core closure would weaken the canonical boundary.

Options considered

OptionBenefitCost
Reuse bridge arity plus AnyMinimal parser workNo sound Core signature
Infer function types from bodies and call sitesLess source syntaxOrder-sensitive, recursive inference policy, silent coercion risk
Supply a separate function manifestExplicit and easy to prototypeSource and signature can drift independently
Require scalar annotations in Surface syntaxOne inspectable source of truthExisting unannotated functions remain outside T2B

Decision

T2B requires annotated direct functions:

~ fn blend($x: F64, $bias: F64) -> F64
    ^ $x + $bias
~ end

The only admitted annotation names are Bool, I64, and F64. Refinement predicates, Num, Any, missing annotations, integer arithmetic, mixed numeric arithmetic, and implicit coercion fail closed.

Top-level function declarations form one contiguous prefix. The remaining statements are the entry body and retain T2A’s ordered typed input manifest and declared result variable. Function ID 0 is the entry; declared functions receive IDs 1 onward in source order.

T2B functions:

  • have unique names and unique parameter names;
  • are closed over their parameters and locals;
  • may directly call any annotated function in the prefix, including forward, recursive, or mutually recursive references;
  • have pure effect rows and no region parameters;
  • admit T2A scalar expressions plus direct calls;
  • contain one explicit, value-bearing, final top-level Return;
  • do not admit early, nested, empty, or implicit returns.

A final direct call is lowered to Core TailCall. Other calls become ANF RValue::Call bindings. Arguments are elaborated left-to-right and must match the exact scalar signature.

T2B admits at most 32 declared functions and 32 parameters per function. Existing global source/Core node caps remain global across the whole artifact. The Core interpreter’s call-depth and step budgets remain the runtime bound for non-tail and tail recursion respectively.

Rationale

  • The signature is versionable, visible, and travels with the source.
  • No bridge Any or heuristic numeric choice crosses into Core.
  • Closed direct functions do not pre-empt the later closure-conversion model.
  • Source-order IDs and per-function local numbering give deterministic artifacts without encoding Surface names.
  • Proper tail calls establish the recursion mechanism needed by later loop elaboration without introducing machine concepts into Core.

Trade-offs

  • Existing unannotated Surface functions still run on bridge backends but fail T2B admission.
  • The first function profile excludes common early-return styles.
  • The parser gains syntax that the coarse bridge typechecker does not make canonical.
  • Recursive source may still exhaust declared runtime budgets.

Consequences

Positive: T2B can exercise Core’s direct-call semantics with exact types, stable hashes, and Surface/Core differential evidence.

Negative: T2B remains a small pure-functional island rather than general Surface function compilation.

Mitigation: diagnostics identify the missing/unsupported annotation, call target, argument, return, or capture. Unsupported syntax never falls back to the bridge interpreter inside elaboration.

Revisit trigger

Widen return control flow after a typed control-flow analysis can prove all paths and still validate unreachable syntax. Replace the three scalar annotations only through a versioned type-syntax decision. Add closures only after existential environment representation, capture typing, effects, and logical lifetime semantics are implemented in Core-N0.