From 16e21ef6fa2e1ed62bbf8932224a51007c7c54d3 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 9 May 2026 13:09:11 +0000 Subject: [PATCH] js-on-sx: Function.prototype.{call,apply,bind,toString} expose spec length/name --- lib/js/runtime.sx | 2 +- plans/js-on-sx.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 5339d60e..a96dab0a 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -23,7 +23,7 @@ ;; ── Boolean coercion (ToBoolean) ────────────────────────────────── -(define js-function-global {:__callable__ (fn (&rest args) (js-function-ctor args)) :prototype {:call (fn (&rest args) (js-invoke-function-method (js-this) "call" args)) :length 0 :bind (fn (&rest args) (js-invoke-function-method (js-this) "bind" args)) :toString (fn () (js-invoke-function-method (js-this) "toString" (list))) :apply (fn (&rest args) (js-invoke-function-method (js-this) "apply" args)) :name ""}}) +(define js-function-global {:__callable__ (fn (&rest args) (js-function-ctor args)) :prototype {:call {:__callable__ (fn (&rest args) (js-invoke-function-method (js-this) "call" args)) :length 1 :name "call"} :length 0 :bind {:__callable__ (fn (&rest args) (js-invoke-function-method (js-this) "bind" args)) :length 1 :name "bind"} :toString {:__callable__ (fn () (js-invoke-function-method (js-this) "toString" (list))) :length 0 :name "toString"} :apply {:__callable__ (fn (&rest args) (js-invoke-function-method (js-this) "apply" args)) :length 2 :name "apply"} :name ""}}) (define js-function-ctor diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 24116450..23aabac1 100644 --- a/plans/js-on-sx.md +++ b/plans/js-on-sx.md @@ -158,6 +158,8 @@ Each item: implement → tests → update progress. Mark `[x]` when tests green. Append-only record of completed iterations. Loop writes one line per iteration: date, what was done, test count delta. +- 2026-05-09 — **`Function.prototype.{call, apply, bind}` carry their spec lengths and names.** Per spec, `Function.prototype.call.length === 1`, `apply.length === 2`, `bind.length === 1`. We were storing them as bare lambdas with `&rest args`, so `js-fn-length` fell back to the param-counting path which yielded 0. Wrapped each in the dict-with-`__callable__` pattern with explicit `length` and `name` slots; `toString` got `length: 0`. Result: built-ins/Function/prototype/apply 18/30 → 22/30, call 17/30 → 18/30. bind 14/30 holds (its remaining failures are deeper bind semantics — bound length, target check). Object 30/30. conformance.sh: 148/148. + - 2026-05-09 — **`Function.prototype.{call, apply, bind, toString}` delegate to the real implementation when invoked through the proto chain.** Was: stub functions returning `:js-undefined` / a no-op closure. So `Number.bind(null)` resolved through `Number.__proto__ === Function.prototype` to the stub bind, which returned `(fn () :js-undefined)` instead of an actual bound function. Replaced each stub with `(fn (&rest args) (js-invoke-function-method (js-this) "" args))`, so the prototype methods route to the same implementation that `js-invoke-method` uses when calling on a lambda directly. Now `Number.bind(null)(42) === 42`. Result: built-ins/Function/prototype/bind 9/30 → 14/30, call 12/30 → 17/30, apply 16/30 → 18/30. Object 30/30 holds. conformance.sh: 148/148. - 2026-05-09 — **Functions inherit through their `__proto__` chain in `js-dict-get-walk`; `fn.prototype = X` actually persists.** Two related fixes around the function-as-object semantics: (1) `js-dict-get-walk` was returning undefined the moment it hit any non-dict in the proto chain — but the chain often runs through a function (e.g. `obj.__proto__ === proto` where `proto` is itself a function returned by `Function()`). Now treats lambda/function/component as if they have `__proto__ === Function.prototype` and continues the walk. (2) `js-set-prop` was a no-op when called on a function with key `"prototype"` (returned val without storing) — so `FACTORY.prototype = proto` silently dropped on the floor. Now redirects to `__js_proto_table__` so the next `new FACTORY` picks up the right proto. Result: built-ins/Function/prototype/call 7/30 → 12/30, apply 12/30 → 16/30. Object 30/30, Map 18/30, Array 18/30 unchanged. conformance.sh: 148/148.