js: uniform this-parameter for plain functions (callback thisArg) - #76
Merged
Conversation
A plain function's `this` was baked to globalThis at lower time, so it could never be a call-time value. Give every top-level function whose body reads `this` an internal `<name>%this` implementation that takes `this` as an explicit leading parameter, and make the exported `<name>/n` a thin wrapper that forwards the sloppy-mode default `this` (the globalThis singleton). This is behaviour-identical on its own — an ordinary `f(...)` call still runs with `this === globalThis` — but it is the groundwork for letting an iteration method's thisArg or `.call`/`.apply`/`.bind` supply a real receiver: those sites can now target the `%this` impl instead of dropping the receiver.
Now that a this-reading top-level function has a <name>%this implementation
taking this as a leading parameter, wire the sites that supply a dynamic
receiver to target it:
- an Array iteration method (map/filter/forEach/some/every/find*) called with
a NAMED callback + thisArg bakes the thisArg as the callback's this;
- f.call(thisArg, ...args) becomes a direct call of f%this with the receiver;
- f.apply(thisArg, argArray) applies a this-baked closure over f%this to the
array's elements (via the now-exported apply_arg_list);
- f.bind(thisArg, ...bound) returns a closure with this fixed to thisArg.
An arrow, a this-less function, or a plain-function VALUE variable is not
matched and keeps the prior behaviour (arrows correctly ignore a thisArg; a
value variable's thisArg is still dropped, a documented v1 gap).
apply_arg_list is exported from the runtime and registered in the emit js-op
allowlist so the apply path can reach it.
Update the module header and the call/apply/iteration dispatch docs to describe the new %this-impl-plus-wrapper lowering, which sites thread a dynamic thisArg, and what is still deferred (a thisArg for a callback held in a variable, and sloppy-mode this->global / primitive-this boxing).
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.
Uniform
thisparameter for plain functions (callbackthisArg)A plain (non-method) function's
thiswas baked toglobalThisat lower time, soit could never be a call-time value. That made every
this-arg/*-5-*cluster intest262's Array iteration methods fail (
this === thisArgfor a named callback), anddropped the receiver on
f.call/apply/bind.Approach — bake
thisat the value boundary (no FFI ABI change)Rather than widening the whole call ABI, this mirrors how class methods already
work:
thisis an explicit leading parameter of the implementation, and is bakedas a capture at the value boundary — so every callable BEAM value keeps its
user-arity and
call_cb/apply_fn/func_*are unchanged.functionwhose body readsthisis lowered as<name>%this/(n+1)taking
thisfirst, with the exported<name>/na thin wrapper that forwards thesloppy-mode default
this(theglobalThissingleton). A plainf(…)call istherefore behaviour-identical.
map/filter/forEach/some/every/find*) with anamed callback +
thisArgbakes thethisArgas the callback'sthis.f.call(t, …)→ direct call off%this;f.apply(t, arr)→ this-baked closureapplied over
arr;f.bind(t, …)→ closure withthisfixed tot.thisArg— lexicalthis).Landed in two green stages: (1) the behaviour-preserving
%this+wrapper split, then(2) the
thisArgwiring.Results (test262, absolute-path suite)
Buckets: Array map/forEach/filter/every/some + Map/Set forEach.
call/apply/bind + find/findIndex (checked pass→fail set-diff: none).
-5-this-argcluster (named callback assertingthis === thisArg).Tests
12 spec-driven tests appended under
// ==== this parameter (wave 14) ====(inline & named callback
thisArg,f.call/.apply/.bindwith a receiver, arrowin a method sees the method's
this, and a plain call'sthisstillglobalThis).Green
Full
gleam test: 2821 passed, 3 failures — the 3 are the pre-existing WASMspec-suite failures (
spec_suite_safe/unsafe,skip_count), identical to the basebranch.
gleam format --check,gleam build(no warnings), and theerlc -WallFFI gate all clean.
Covered vs deferred
thisArgfor Array iteration;f.call/apply/bind(t, …)on a named
this-reading function; inline-callbackthisArg(wave 12, preserved);arrows ignore
thisArg.thisArgfor a callback held in a VARIABLE(a function-expression value or
let g = f; g.call(t)— the value carries nooverridable
this); Map/SetforEachthisArg(its test262 files use a variablecallback → the same value gap); sloppy-mode
this→global and primitive-thisboxing for a directly-invoked function; bound-function
.length/.name.Merge-conflict surface
Touches function-lowering + call-site dispatch in
src/twocore/frontend/js/lower.gleam:lower_function(now returns#(exported, extras)),program,lower_class(statics),lower_instance_methodand
lower_instance_method_thisarg(new interception +lower_named_call/_apply/_bind/_this_iter), plus a newCtx.this_fnsfield. Also addsapply_arg_listtort_js.gleam, the FFI export list, and the emit js-op allowlist.