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
| Option | Benefit | Cost |
|---|---|---|
Reuse bridge arity plus Any | Minimal parser work | No sound Core signature |
| Infer function types from bodies and call sites | Less source syntax | Order-sensitive, recursive inference policy, silent coercion risk |
| Supply a separate function manifest | Explicit and easy to prototype | Source and signature can drift independently |
| Require scalar annotations in Surface syntax | One inspectable source of truth | Existing 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
Anyor 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.