/** * Constant folding entry point for the type inference engine. * * Given a direct `Call` to a builtin with all-literal argument types and * an empty inferred effect set, synthesize a Call AST whose args are * literal nodes, hand it to the fold sandbox, and wrap the runtime result * back as a `Literal` type. * * Phase C v1 scope (primitives only): * - Callee: `NodeTypes.Builtin` references only — user-defined and * module-imported functions come in follow-ups (decision #5, #13). * - Argument types: primitive literals (`Literal` of number/string/boolean), * atoms (`Atom`), and `Null`. Closed tuples and records come in follow-ups * (decision #10). * * Gate: callers must check `FOLD_ENABLED` before invoking. This file has no * knowledge of the toggle — it's pure fold mechanics. * * See design docs: * - design/archive/2026-04-16_constant-folding-in-types.md * - design/archive/2026-04-16_builtin-effect-audit.md * - design/archive/2026-04-16_fold-toggle-and-differential-tests.md */ import type { AstNode } from '../parser/types'; import type { Type } from './types'; /** * Reconstruct a literal AST node from an inferred type. * * Supports primitives (Literal, Atom, Null) and closed composites (Tuple, * closed Record) with arbitrary nesting — per decision #10. Bails on: * - Plain `Number` / `String` / `Boolean` (no concrete value). * - Open records (`open: true`) — can't know all fields. * - `Array` types (element-only, no length info). * - Function values, `Unknown`, type vars. * * Exported so the C6a closure-capture reconstruction path in `infer.ts` * can use the same machinery to build let-binding values for captures. */ export declare function literalTypeToAstNode(t: Type): AstNode | null; export interface FoldOutcome { /** Folded successfully — use this as the inferred result type. */ type?: Type; /** Fold surfaced an effect (typically `@dvala.error`) — caller should * emit a warning and fall back to the normal inferred type. */ effectName?: string; } /** * Attempt to fold a direct builtin Call with all-primitive-literal args. * * @param calleeNode The AST node of the callee (must be a Builtin node). * @param argTypes The inferred types of each argument. * @returns * - `{ type }` on success: the literal type of the folded result. * - `{ effectName }` when the fold performed an effect the sandbox caught * (caller emits a `severity: 'warning'` diagnostic). * - `null` when the call isn't eligible for folding (non-builtin * callee, non-literal args, budget exhaustion, or any * unhandled sandbox failure — silent fallback). */ export declare function tryFoldBuiltinCall(calleeNode: AstNode, argTypes: Type[]): FoldOutcome | null; /** * Recursively collect free symbol references (`NodeTypes.Sym`) reachable * from an AST subtree — i.e. references that are NOT bound by a * containing parameter or `let` within the same AST. Used by C6a to * enumerate closure-capture candidates. * * Why filter locals out: if the function body shadows an outer name via * `let x = …`, including `x` in the capture set would make * `infer.ts`'s capture loop query the outer type — and if that type * happens to be non-literal, the whole fold bails. The body wouldn't * actually use the outer value (the inner `let` shadows it), so * dropping the reference keeps more programs foldable without loss of * correctness. Same reasoning applies to function parameters and to * names bound by enclosing `let`s inside the function body. * * Implementation note: a single-pass walker that tracks a running set * of locally-bound names. Descends into `Let` bindings and `Function` * nodes with the appropriate scope introduction. The AST's uniformly * `[type, payload, id]` shape lets us recognise Sym nodes by their * first element. */ export declare function collectSymRefs(ast: AstNode): Set; /** * Attempt to fold a Call to a user-defined function (C6 + C6a). * * @param functionAst The Function-node AST that the caller's binding * resolves to (captured via `env.bindFunctionAst`). * @param argTypes The inferred types of each argument. * @param captures Map of free-variable name → reconstructed value AST. * The caller (inferExpr) walks the function body for * symbol references, resolves each through the outer * TypeEnv, and converts literal-typed ones via * `literalTypeToAstNode`. Capture entries become * `let name = ` bindings wrapped around the * synthesized Call, so the sandbox can resolve them. * @returns Same contract as `tryFoldBuiltinCall`. * * How it works: we build a Block AST `do let c1 = v1; … let cN = vN; ()() end`, * evaluate through the sandbox, and lift the result back to a type. * Function-parameter shadowing works naturally — when a capture name * matches a function param, the param wins inside the body. * * When `captures` is empty (e.g. the function is closure-free), the * Block wrapping is skipped and we use the raw Call. * * Free variables the caller *didn't* include in `captures` are assumed * to be builtins (resolved globally by the sandbox's context stack) or * locally bound inside the function body. If either assumption fails — * typically because a capture wasn't reconstructible and the caller * passed a partial map — the sandbox raises ReferenceError, which * `evaluateNodeForFold` surfaces as `reason: 'error'` (silent fallback), * not as a warning. */ export declare function tryFoldUserFunctionCall(functionAst: AstNode, argTypes: Type[], captures: Map): FoldOutcome | null;