js: this-binding for callbacks + generic .call/.apply - #69
Merged
Conversation
…totype methods
Rewrite a static <Ctor>.prototype.<m>.call(recv, ...args) / .apply(recv, [args])
on a built-in prototype method (Array/String/Object/Number/...) to the ordinary
method call recv.<m>(...args). In this receiver-first model the unbound method
must run with recv as its this, so this reuses the whole tested method-dispatch
path and forwards the receiver correctly. Previously func_call/func_apply dropped
the receiver as the ignored thisArg, so e.g. String.prototype.trim.call(x) trimmed
undefined instead of x.
Give the String.prototype receiver-coercion hook str_this its full spec meaning
(ToString(RequireObjectCoercible(this))): undefined/null throw a TypeError, every
non-string primitive/array/object is ToString-coerced, plain strings pass through.
This flows to every string method that already routes through str_this (upper,
lower, char_at, split, trim family) and is what makes trim.call(0) yield "0",
.call(false) "false", .call([1]) "1".
Honor the thisArg of the Array iteration methods for an INLINE function callback
that reads this, by binding the callback closure's this capture to the thisArg
(the same mechanism object accessors use). A callback passed by name still ignores
thisArg, since a top-level function has no this parameter; documented as a v1 gap.
test262 built-ins/{Array/prototype/{map,filter,forEach},String/prototype/trim,
Object/prototype/hasOwnProperty}: 202 -> 241 pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two related pieces of
this-plumbing, targeting the largest remaining test262 blocker after wave 10 bound the built-in constructors.Bucket 2 — generic
.call/.applyon a built-in prototype method value (landed)A static
<Ctor>.prototype.<m>.call(recv, …args)/.apply(recv, [args])on a built-in prototype method (Array/String/Object/Number/Boolean/Date/RegExp/Map/Set/Function) is rewritten to the ordinary method callrecv.<m>(…args). In this receiver-first model the unbound method must run withrecvas itsthis, so the rewrite reuses the entire tested method-dispatch path and forwards the receiver first. Previouslyfunc_call/func_applydropped the receiver as the ignoredthisArg(e.g.String.prototype.trim.call(x)trimmedundefined)..applyis unrolled only when its argument array is a literal; otherwise it falls through.Also gave the String receiver-coercion hook
str_thisits full spec meaning —ToString(RequireObjectCoercible(this)):undefined/nullthrow a TypeError, every non-string primitive/array/object isToString-coerced, plain strings pass through. This flows to every string method already routing throughstr_this(upper/lower/charAt/split/trim family), sotrim.call(0)→"0",.call(false)→"false",.call([1])→"1",.call(undefined)throws.Bucket 1 — thisArg on iteration callbacks (partial)
The
thisArgof the Array iteration methods (map/filter/forEach/some/every/find/findIndex/findLast/findLastIndex) is now honored when the callback is an INLINEfunction(){…}expression that readsthis— the callback closure'sthiscapture is bound to thethisArg, the same mechanism object getters/setters already use. Arrows correctly ignorethisArg(lexicalthis).Deferred (documented): a callback passed by NAME (
arr.map(callbackfn, thisArg)— the shape most test262this-argfiles use) still ignoresthisArg. A top-level function carries nothisparameter (itsthisresolves toglobalThisat lower time), so binding a dynamic receiver would require makingthisa call-time parameter of every plain function — a cross-cutting ABI change touching all call sites,apply_fn/call_cb,func_call/func_apply, and every iteration op. Out of scope for this wave.Before → after (test262)
built-ins/{Array/prototype/{map,filter,forEach}, String/prototype/trim, Object/prototype/hasOwnProperty}:202 → 241 pass (+39). Nearly all of the movement is bucket 2 (the
String.prototype.trim.call(primitive)cluster + slice/hasOwnProperty.call). Bucket 1's inline-only subset moves ~0 test262 files (they use named callbacks) but is spec-correct and lands the requested unit behavior.Tests
Full
gleam testgreen (2781 passed; the only 3 failures are the pre-existing missing WASM.watconformance fixtures, unrelated). Added 9 spec-driven tests under// ==== this-binding + call/apply (wave 12) ====coveringArray.prototype.slice.call/.apply,String.prototype.trim.call(string + coercion + RequireObjectCoercible throw),Object.prototype.hasOwnProperty.call, inline-callbackmap/forEachthisArg, and arrow-ignores-thisArg.Files
src/twocore/frontend/js/lower.gleam—.call/.applyrewrite + inline-callback thisArg binding + header docssrc/twocore_rt_js_ffi.erl—str_thisfull ToString/RequireObjectCoercible; trim family routes through it (no-exportchanges)test/js_compiler_test.gleam— wave-12 tests