mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Bind thisArg in the array callback methods of dynamic arrays
forEach, map, filter, some, every, find, findIndex, findLast, findLastIndex and flatMap on a dynamic array, and through Array.prototype.<method>.call on a native array, called the callback without its thisArg, so `this` in the callback was not the second argument. These methods now bind `this` to the thisArg, or undefined when there is none, once per call, around all of the method's callbacks.
This commit is contained in:
@@ -916,6 +916,117 @@ void scr_dyn_invoke_discard_id(ScrDyn *recv, ScrDyn *callee, const char *method,
|
||||
scr_dyn_release(scr_dyn_invoke_prepared_id(recv, callee, method, mid_slot, args, argc, what));
|
||||
}
|
||||
|
||||
/* The callback methods of a dense dyn array that take a thisArg:
|
||||
* forEach, map, filter, some, every, find, findIndex, findLast,
|
||||
* findLastIndex and flatMap. The caller binds `this` for the callbacks.
|
||||
* `len` is the length before the first callback. */
|
||||
static ScrDyn *dyn_arr_callback_method(ScrDyn *recv, int mid, ScrDyn *const *args, size_t argc,
|
||||
size_t len, ScrDyn *callback_recv) {
|
||||
if ((mid == DYN_M_findLast) || (mid == DYN_M_findLastIndex)) {
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
|
||||
for (size_t i = len; i > 0; i--) {
|
||||
size_t index = i - 1;
|
||||
ScrDyn *item = index < recv->v.arr.len
|
||||
? scr_dyn_retain(recv->v.arr.items[index])
|
||||
: scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *result = dyn_call_cb(args[0], item, index, visible_recv);
|
||||
if (!result) { scr_dyn_release(item); return NULL; }
|
||||
bool matched = scr_dyn_truthy(result);
|
||||
scr_dyn_release(result);
|
||||
if (matched) {
|
||||
if ((mid == DYN_M_findLast)) return item;
|
||||
scr_dyn_release(item);
|
||||
return scr_dyn_new_num((double)index);
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
return (mid == DYN_M_findLast)
|
||||
? scr_dyn_retain(scr_dyn_undefined()) : scr_dyn_new_num(-1);
|
||||
}
|
||||
if ((mid == DYN_M_forEach) || (mid == DYN_M_map) ||
|
||||
(mid == DYN_M_filter) || (mid == DYN_M_some) ||
|
||||
(mid == DYN_M_every) || (mid == DYN_M_find) ||
|
||||
(mid == DYN_M_findIndex)) {
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *cb = args[0];
|
||||
ScrDyn *out = ((mid == DYN_M_map) || (mid == DYN_M_filter)) ? scr_dyn_new_arr() : NULL;
|
||||
/* Array iteration methods snapshot length before the first callback.
|
||||
* A shrinking callback can remove a later dense entry; an expanding
|
||||
* callback must not make the new tail observable to this pass. Live
|
||||
* Web-boundary capsules are the externally visible receiver, so pass
|
||||
* that capsule as callback arg 3: mutations through the callback's
|
||||
* array argument then commit directly to the original static array. */
|
||||
size_t n = len;
|
||||
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
|
||||
for (size_t step = 0; step < n; step++) {
|
||||
size_t i = step;
|
||||
if (i >= recv->v.arr.len) continue;
|
||||
ScrDyn *item = scr_dyn_retain(recv->v.arr.items[i]);
|
||||
ScrDyn *r = dyn_call_cb(cb, item, i, visible_recv);
|
||||
if (!r) { scr_dyn_release(item); scr_dyn_release(out); return NULL; }
|
||||
if ((mid == DYN_M_map)) {
|
||||
scr_dyn_arr_push(out, r); /* ownership moves in */
|
||||
r = NULL;
|
||||
} else {
|
||||
bool truthy = scr_dyn_truthy(r);
|
||||
scr_dyn_release(r);
|
||||
if ((mid == DYN_M_filter) && truthy) scr_dyn_arr_push(out, scr_dyn_retain(item));
|
||||
if ((mid == DYN_M_some) && truthy) { scr_dyn_release(item); return scr_dyn_new_bool(true); }
|
||||
if ((mid == DYN_M_every) && !truthy) { scr_dyn_release(item); return scr_dyn_new_bool(false); }
|
||||
if ((mid == DYN_M_find) && truthy) return item; /* +1 moves out */
|
||||
if ((mid == DYN_M_findIndex) && truthy) { scr_dyn_release(item); return scr_dyn_new_num((double)i); }
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
if (out) return out;
|
||||
if ((mid == DYN_M_some)) return scr_dyn_new_bool(false);
|
||||
if ((mid == DYN_M_every)) return scr_dyn_new_bool(true);
|
||||
if ((mid == DYN_M_find)) return scr_dyn_retain(scr_dyn_undefined());
|
||||
if ((mid == DYN_M_findIndex)) return scr_dyn_new_num(-1);
|
||||
return scr_dyn_retain(scr_dyn_undefined()); /* forEach */
|
||||
}
|
||||
if ((mid == DYN_M_flatMap)) {
|
||||
/* JS Array.prototype.flatMap over a dyn array: map + a depth-1
|
||||
* flatten. Native dyn-array results flatten element-by-element; a
|
||||
* WRAPPED engine array flattens through the routed keyed reads
|
||||
* (elements wrap back scalar-normalized); everything else pushes
|
||||
* as a single element (JS keeps non-array callback results whole).
|
||||
* JS's spec snapshots the length up front (elements appended by
|
||||
* the callback are not visited). */
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *cb = args[0];
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
size_t n = recv->v.arr.len;
|
||||
for (size_t i = 0; i < n && i < recv->v.arr.len; i++) {
|
||||
if (!dyn_borrow_present(recv, i)) { if (scr_exc_pending()) { scr_dyn_release(out); return NULL; } continue; }
|
||||
ScrDyn *item = dyn_borrow_item(recv, i);
|
||||
if (!item) { scr_dyn_release(out); return NULL; }
|
||||
ScrDyn *r = dyn_call_cb(
|
||||
cb, item, i, callback_recv ? callback_recv : recv);
|
||||
scr_dyn_release(item);
|
||||
if (!r) { scr_dyn_release(out); return NULL; }
|
||||
if (r->kind == SCR_DYN_TYPED_REF && scr_dyn_ref_is_array(r)) {
|
||||
ScrDyn *view = scr_dyn_typed_ref_materialize(r);
|
||||
scr_dyn_release(r);
|
||||
r = view;
|
||||
if (!r) { scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
if (r->kind == SCR_DYN_ARR) {
|
||||
for (size_t j = 0; j < r->v.arr.len; j++) {
|
||||
if (dyn_borrow_present(r, j)) scr_dyn_arr_push(out, dyn_borrow_item(r, j));
|
||||
if (scr_exc_pending()) { scr_dyn_release(r); scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
scr_dyn_release(r);
|
||||
} else {
|
||||
scr_dyn_arr_push(out, r); /* ownership moves in */
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_dyn_invoke_impl(
|
||||
ScrDyn *recv, const char *method, int mid, ScrDyn *const *args, size_t argc,
|
||||
const char *what, ScrDyn *callback_recv) {
|
||||
@@ -1367,27 +1478,16 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
}
|
||||
return (mid == DYN_M_includes) ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1);
|
||||
}
|
||||
if ((mid == DYN_M_findLast) || (mid == DYN_M_findLastIndex)) {
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
|
||||
for (size_t i = len; i > 0; i--) {
|
||||
size_t index = i - 1;
|
||||
ScrDyn *item = index < recv->v.arr.len
|
||||
? scr_dyn_retain(recv->v.arr.items[index])
|
||||
: scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *result = dyn_call_cb(args[0], item, index, visible_recv);
|
||||
if (!result) { scr_dyn_release(item); return NULL; }
|
||||
bool matched = scr_dyn_truthy(result);
|
||||
scr_dyn_release(result);
|
||||
if (matched) {
|
||||
if ((mid == DYN_M_findLast)) return item;
|
||||
scr_dyn_release(item);
|
||||
return scr_dyn_new_num((double)index);
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
return (mid == DYN_M_findLast)
|
||||
? scr_dyn_retain(scr_dyn_undefined()) : scr_dyn_new_num(-1);
|
||||
if ((mid == DYN_M_forEach) || (mid == DYN_M_map) || (mid == DYN_M_filter) ||
|
||||
(mid == DYN_M_some) || (mid == DYN_M_every) || (mid == DYN_M_find) ||
|
||||
(mid == DYN_M_findIndex) || (mid == DYN_M_findLast) || (mid == DYN_M_findLastIndex) ||
|
||||
(mid == DYN_M_flatMap)) {
|
||||
/* Every callback runs with `this` bound to the thisArg, or undefined
|
||||
* when there is none. */
|
||||
scr_dyn_this_push_dyn(argc > 1 ? args[1] : scr_dyn_undefined());
|
||||
ScrDyn *result = dyn_arr_callback_method(recv, mid, args, argc, len, callback_recv);
|
||||
scr_dyn_this_pop();
|
||||
return result;
|
||||
}
|
||||
if ((mid == DYN_M_join)) {
|
||||
ScrJsonBuf b;
|
||||
@@ -1550,48 +1650,6 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
}
|
||||
return scr_dyn_retain(recv);
|
||||
}
|
||||
if ((mid == DYN_M_forEach) || (mid == DYN_M_map) ||
|
||||
(mid == DYN_M_filter) || (mid == DYN_M_some) ||
|
||||
(mid == DYN_M_every) || (mid == DYN_M_find) ||
|
||||
(mid == DYN_M_findIndex)) {
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *cb = args[0];
|
||||
ScrDyn *out = ((mid == DYN_M_map) || (mid == DYN_M_filter)) ? scr_dyn_new_arr() : NULL;
|
||||
/* Array iteration methods snapshot length before the first callback.
|
||||
* A shrinking callback can remove a later dense entry; an expanding
|
||||
* callback must not make the new tail observable to this pass. Live
|
||||
* Web-boundary capsules are the externally visible receiver, so pass
|
||||
* that capsule as callback arg 3: mutations through the callback's
|
||||
* array argument then commit directly to the original static array. */
|
||||
size_t n = len;
|
||||
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
|
||||
for (size_t step = 0; step < n; step++) {
|
||||
size_t i = step;
|
||||
if (i >= recv->v.arr.len) continue;
|
||||
ScrDyn *item = scr_dyn_retain(recv->v.arr.items[i]);
|
||||
ScrDyn *r = dyn_call_cb(cb, item, i, visible_recv);
|
||||
if (!r) { scr_dyn_release(item); scr_dyn_release(out); return NULL; }
|
||||
if ((mid == DYN_M_map)) {
|
||||
scr_dyn_arr_push(out, r); /* ownership moves in */
|
||||
r = NULL;
|
||||
} else {
|
||||
bool truthy = scr_dyn_truthy(r);
|
||||
scr_dyn_release(r);
|
||||
if ((mid == DYN_M_filter) && truthy) scr_dyn_arr_push(out, scr_dyn_retain(item));
|
||||
if ((mid == DYN_M_some) && truthy) { scr_dyn_release(item); return scr_dyn_new_bool(true); }
|
||||
if ((mid == DYN_M_every) && !truthy) { scr_dyn_release(item); return scr_dyn_new_bool(false); }
|
||||
if ((mid == DYN_M_find) && truthy) return item; /* +1 moves out */
|
||||
if ((mid == DYN_M_findIndex) && truthy) { scr_dyn_release(item); return scr_dyn_new_num((double)i); }
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
if (out) return out;
|
||||
if ((mid == DYN_M_some)) return scr_dyn_new_bool(false);
|
||||
if ((mid == DYN_M_every)) return scr_dyn_new_bool(true);
|
||||
if ((mid == DYN_M_find)) return scr_dyn_retain(scr_dyn_undefined());
|
||||
if ((mid == DYN_M_findIndex)) return scr_dyn_new_num(-1);
|
||||
return scr_dyn_retain(scr_dyn_undefined()); /* forEach */
|
||||
}
|
||||
if ((mid == DYN_M_reduce) || (mid == DYN_M_reduceRight)) {
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
bool backward = (mid == DYN_M_reduceRight);
|
||||
@@ -1616,44 +1674,6 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
if ((mid == DYN_M_flatMap)) {
|
||||
/* JS Array.prototype.flatMap over a dyn array: map + a depth-1
|
||||
* flatten. Native dyn-array results flatten element-by-element; a
|
||||
* WRAPPED engine array flattens through the routed keyed reads
|
||||
* (elements wrap back scalar-normalized); everything else pushes
|
||||
* as a single element (JS keeps non-array callback results whole).
|
||||
* JS's spec snapshots the length up front (elements appended by
|
||||
* the callback are not visited). */
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *cb = args[0];
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
size_t n = recv->v.arr.len;
|
||||
for (size_t i = 0; i < n && i < recv->v.arr.len; i++) {
|
||||
if (!dyn_borrow_present(recv, i)) { if (scr_exc_pending()) { scr_dyn_release(out); return NULL; } continue; }
|
||||
ScrDyn *item = dyn_borrow_item(recv, i);
|
||||
if (!item) { scr_dyn_release(out); return NULL; }
|
||||
ScrDyn *r = dyn_call_cb(
|
||||
cb, item, i, callback_recv ? callback_recv : recv);
|
||||
scr_dyn_release(item);
|
||||
if (!r) { scr_dyn_release(out); return NULL; }
|
||||
if (r->kind == SCR_DYN_TYPED_REF && scr_dyn_ref_is_array(r)) {
|
||||
ScrDyn *view = scr_dyn_typed_ref_materialize(r);
|
||||
scr_dyn_release(r);
|
||||
r = view;
|
||||
if (!r) { scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
if (r->kind == SCR_DYN_ARR) {
|
||||
for (size_t j = 0; j < r->v.arr.len; j++) {
|
||||
if (dyn_borrow_present(r, j)) scr_dyn_arr_push(out, dyn_borrow_item(r, j));
|
||||
if (scr_exc_pending()) { scr_dyn_release(r); scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
scr_dyn_release(r);
|
||||
} else {
|
||||
scr_dyn_arr_push(out, r); /* ownership moves in */
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if ((mid == DYN_M_sort)) {
|
||||
ScrDyn *cmp = argc > 0 ? args[0] : scr_dyn_undefined();
|
||||
if (cmp->kind != SCR_DYN_UNDEF && cmp->kind != SCR_DYN_FUNC) {
|
||||
|
||||
@@ -20,3 +20,46 @@ console.log(words.join(","), Array.prototype.includes.call(words, "a"));
|
||||
const records: { id: number }[] = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
||||
Array.prototype.copyWithin.call(records, 1, 0, 1);
|
||||
console.log(records.map((r) => r.id).join(","), records[0] === records[1]);
|
||||
|
||||
// The callback methods bind `this` to their thisArg, for native arrays
|
||||
// through .call and for dynamic arrays called directly.
|
||||
type Scale = { k: number; seen: number[] };
|
||||
const scale: Scale = { k: 10, seen: [] };
|
||||
const values: number[] = [1, 2, 3, 4];
|
||||
Array.prototype.forEach.call(values, function (this: Scale, v: number) {
|
||||
this.seen.push(v);
|
||||
}, scale);
|
||||
console.log(scale.seen.join(","));
|
||||
console.log(Array.prototype.map.call(values, function (this: Scale, v: number) {
|
||||
return v * this.k;
|
||||
}, scale).join(","));
|
||||
console.log(Array.prototype.filter.call(values, function (this: Scale, v: number) {
|
||||
return v * this.k > 15;
|
||||
}, scale).join(","));
|
||||
console.log(
|
||||
Array.prototype.some.call(values, function (this: Scale, v: number) { return v === this.k / 5; }, scale),
|
||||
Array.prototype.every.call(values, function (this: Scale, v: number) { return v < this.k; }, scale),
|
||||
Array.prototype.find.call(values, function (this: Scale, v: number) { return v * this.k === 30; }, scale),
|
||||
Array.prototype.findIndex.call(values, function (this: Scale, v: number) { return v * this.k === 30; }, scale),
|
||||
Array.prototype.findLast.call(values, function (this: Scale, v: number) { return v * this.k < 30; }, scale),
|
||||
Array.prototype.findLastIndex.call(values, function (this: Scale, v: number) { return v * this.k < 30; }, scale),
|
||||
);
|
||||
console.log(Array.prototype.flatMap.call(values, function (this: unknown, v: number) {
|
||||
return [v, v * (this as Scale).k];
|
||||
}, scale).join(","));
|
||||
const parsed: any = JSON.parse("[1, 2, 3]");
|
||||
const sum = { total: 0 };
|
||||
parsed.forEach(function (this: { total: number }, v: number) {
|
||||
this.total += v;
|
||||
}, sum);
|
||||
console.log(sum.total, parsed.map(function (this: Scale, v: number) { return v * this.k; }, scale).join(","));
|
||||
class Counter {
|
||||
total = 0;
|
||||
addAll(items: number[]): number {
|
||||
Array.prototype.forEach.call(items, (v: number) => {
|
||||
this.total += v;
|
||||
});
|
||||
return this.total;
|
||||
}
|
||||
}
|
||||
console.log(new Counter().addAll(values));
|
||||
|
||||
Reference in New Issue
Block a user