Skip to content

Commit 26a73a0

Browse files
fix(interp): wire missing string_length builtin (Refs #332, #329) (#362)
## Summary `string_length : String -> Int / Pure` is declared as a primitive in three places that AffineScript programs traverse during the compile pipeline: | Layer | File | State | |---|---|---| | Surface decl | `stdlib/effects.affine` | ✓ declared | | Resolver | `lib/resolve.ml` | ✓ recognised | | Typechecker | `lib/typecheck.ml` | ✓ typed `String -> Int / EPure` | | Codegen (Deno) | `lib/codegen_deno.ml` | ✓ `(s).length` | | **Interpreter** | **`lib/interp.ml`** | **❌ MISSING `VBuiltin` entry** | Programs calling `string_length` compile cleanly and fail at run with `Unbound variable: string_length`. ## What this breaks (without the fix) Two E2E test suites currently red on main: - `E2E STDLIB-04e Pure #332` / `"string_length(\"hello\") == 5"` — direct miss. - `E2E STDLIB-04b error #329` / `error_diverges_string_call_site` — uses `string_length(k) > 0` as a guard, so the missing binding masked the `RuntimeError "empty key"` assertion as an `Unbound variable` error. ## Fix One-block addition to `create_initial_env`'s builtin table, right after `int_to_string` / `float_to_string`. Matches the resolver/typechecker contract and the codegen-Deno semantics. 14 lines including the explanatory comment. ```ocaml ("string_length", VBuiltin ("string_length", fun args -> match args with | [VString s] -> Ok (VInt (String.length s)) | _ -> Error (TypeMismatch "string_length expects String") )); ``` ## How this was found affinescript#361's `build` job was red after that PR's bench/dune + fixture-exemption fixes cleared the other two baselines. The two failing E2E tests both pointed at the same root cause — surface-decl/resolver/typechecker all knew `string_length`, but the interp didn't. ## Test plan - [ ] `dune runtest` passes the STDLIB-04e suite (4 cases) and STDLIB-04b suite - [ ] No regression in `Lexer` / `Parser` / `Typechecker` / `Resolver` / other E2E suites (this is purely additive in the builtin table) Refs #332 (STDLIB-04e), Refs #329 (STDLIB-04b). Companion to #361 — landing this first will turn #361's `build` job green. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b89eb1a commit 26a73a0

1 file changed

Lines changed: 14 additions & 0 deletions

File tree

lib/interp.ml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,20 @@ let create_initial_env () : env =
623623
| [VFloat f] -> Ok (VString (string_of_float f))
624624
| _ -> Error (TypeMismatch "float_to_string expects Float")
625625
));
626+
(* STDLIB-04e (Refs #332): `string_length : String -> Int / Pure` is
627+
declared in stdlib/effects.affine, resolved by lib/resolve.ml, and
628+
typed by lib/typecheck.ml — but the runtime VBuiltin entry was
629+
missing here. Programs that call `string_length` would compile
630+
cleanly and fail at run with `Unbound variable: string_length`
631+
(broke E2E STDLIB-04e #332 + STDLIB-04b #329 — the latter uses
632+
`string_length(k) > 0` as a guard, so the missing binding masked
633+
the divergence-vs-RuntimeError assertion as an Unbound variable
634+
error instead). Wire it as the canonical String.length surface. *)
635+
("string_length", VBuiltin ("string_length", fun args ->
636+
match args with
637+
| [VString s] -> Ok (VInt (String.length s))
638+
| _ -> Error (TypeMismatch "string_length expects String")
639+
));
626640
(* STDLIB-04e (Refs #332): `string_to_int` is the typed-alias surface
627641
declared in stdlib/effects.affine. Same semantics as `parse_int`
628642
(the canonical name) — the alias was unwired before this PR, so

0 commit comments

Comments
 (0)