Skip to content

Commit 3bee36c

Browse files
proggeramlugRalphclaude
authored
fix(runtime): #5587 — subclassing a Temporal.<Type> gives the instance the Temporal brand (#5672)
* fix(runtime): #5587 — subclassing a Temporal.<Type> constructor gives the instance the Temporal brand `class X extends Temporal.Duration { constructor(){ super(...args) } }` produced an empty plain object: `super()` ran the native Temporal constructor (which returns a fresh NaN-boxed cell rather than mutating the implicit `this`) and DISCARDED the returned cell. The subclass instance then had no brand, so `instance.abs()` resolved `abs` to `undefined` and threw `TypeError: value is not a function` — the dominant failure (36 cases) in the test262 `built-ins/Temporal/**/subclassing-ignored.js` cluster. Mirror the existing `class X extends Request/Response` fetch-handle pattern: when a `super()` parent resolves to a Temporal constructor, run it and stash the returned cell on `this` under `__perry_temporal_cell__`. Both `super()` lowerings are covered — `js_fetch_or_value_super` (non-spread `super(a, b)`) and `js_super_construct_apply` (the `super(...spread)` form the helper actually uses, via the decl-time-recorded dynamic parent value). Method-call, property-get, and `instanceof` dispatch recover the cell from there. Also fixes the surrounding real-cell gaps the same tests exercise: - `Object.getPrototypeOf(temporalCell)` returned `null`; now resolves `Temporal.<Type>.prototype` via the live namespace, so `assert.sameValue(Object.getPrototypeOf(result), construct.prototype)` holds. - Reading a prototype method as a value (`d.abs`, not `d.abs()`) returned `undefined`, breaking the `instance[method](...spread)` read+apply form; now returns a bound method (gated on a real per-kind method predicate so unknown properties still read as `undefined`). Applies to real cells and subclass instances. - Construct-only Temporal ctors (`PlainDate`/`PlainTime`/`Instant`/…) threw "requires 'new'" when invoked from `super()`; `new.target` is now set for the call. Known remaining limitation (separate, non-Temporal bug): a class method/constructor defined inside a function captures an enclosing `let` by value, not by cell, so the helper's `assert.sameValue(called, 1)` still fails for in-function subclasses. This affects all classes (reproduces with a plain user-class subclass), not just Temporal, and is out of scope here; with it the subclassing-ignored cases now run all the way through the Temporal logic and fail only on that `called` assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix+style: #5587 — address CodeRabbit review (aliased/raw-I64 receivers) + rustfmt/clippy - fetch_globals: non-spread `super()` now recovers the Temporal parent from the decl-time class-id stash when the immediate heritage value is a stale alias (`const D = Temporal.Duration; class X extends D`), mirroring the Request/Response recovery. (CodeRabbit major) - instanceof: the Temporal subclass-brand check now also accepts the raw-I64 receiver form (top16 == 0), how module-level object vars are stored, not just the NaN-boxed 0x7FFD form. (CodeRabbit minor) - has_method: `matches!(x, Some(_))` → `.is_some()`; rustfmt. - New regression test for the aliased-heritage path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Ralph <ralph@skelpo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c9d156b commit 3bee36c

12 files changed

Lines changed: 585 additions & 8 deletions

File tree

crates/perry-runtime/src/object/class_constructors.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,35 @@ pub unsafe extern "C" fn js_super_construct_apply(
379379
cur = next;
380380
depth += 1;
381381
}
382+
// No registered Perry ancestor constructor. A `class X extends
383+
// Temporal.<Type>` heritage records its parent VALUE (the Temporal ctor
384+
// closure) at decl time but no class-id edge, so the walk above finds
385+
// nothing. Recover that value and, if it is a Temporal constructor, run it
386+
// and stash the returned cell as the subclass instance's brand — the
387+
// `super(...spread)` counterpart of the `js_fetch_or_value_super` branch
388+
// that handles non-spread `super(a, b)`. (#5587)
389+
#[cfg(feature = "temporal")]
390+
{
391+
let parent_val = crate::object::class_registry::js_get_dynamic_parent_value(child_cid);
392+
if crate::object::global_this::temporal_ctor_kind(parent_val).is_some() {
393+
let this_box = crate::value::js_nanbox_pointer(this_raw);
394+
let n = if arr.is_null() {
395+
0
396+
} else {
397+
crate::array::js_array_length(arr)
398+
} as usize;
399+
let mut flat: Vec<f64> = Vec::with_capacity(n);
400+
for i in 0..n {
401+
flat.push(crate::array::js_array_get_f64(arr, i as u32));
402+
}
403+
crate::object::global_this::temporal_subclass_super(
404+
parent_val,
405+
this_box,
406+
flat.as_ptr(),
407+
flat.len(),
408+
);
409+
}
410+
}
382411
undef
383412
}
384413

crates/perry-runtime/src/object/field_get_set.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,49 @@ pub(crate) unsafe fn fetch_subclass_handle_id(obj: usize) -> Option<i64> {
4646
}
4747
}
4848

49+
/// Hidden own-field name under which a `class X extends Temporal.<Type>`
50+
/// instance stashes the NaN-boxed pointer to its underlying Temporal cell.
51+
/// Written by `js_fetch_or_value_super` (the runtime-value super dispatcher,
52+
/// global_this/fetch_globals.rs) when the resolved parent is a Temporal
53+
/// constructor; read here (getter forward), in `native_call_method.rs`
54+
/// (method forward), and in `instanceof.rs`. A Temporal value is a NaN-boxed
55+
/// cell that dispatches via brand arms, not a JS prototype chain, so a subclass
56+
/// instance (a plain heap object) can only reach its members through this
57+
/// stashed cell. Stored as a real pointer-valued field so GC keeps the cell
58+
/// alive and rewrites the slot on evacuation. (#5587)
59+
#[cfg(feature = "temporal")]
60+
pub(crate) const TEMPORAL_SUBCLASS_CELL_FIELD: &[u8] = b"__perry_temporal_cell__";
61+
62+
/// If `obj` (a raw heap object address) is a `class X extends Temporal.<Type>`
63+
/// instance, return the NaN-boxed value of its stashed Temporal cell. Returns
64+
/// `None` for any non-object / non-subclass receiver (so callers fall through
65+
/// to their normal dispatch unchanged) or if the stashed value is somehow no
66+
/// longer a live Temporal cell.
67+
#[cfg(feature = "temporal")]
68+
pub(crate) unsafe fn temporal_subclass_cell(obj: usize) -> Option<f64> {
69+
if obj < crate::gc::GC_HEADER_SIZE + 0x1000 || !is_valid_obj_ptr(obj as *const u8) {
70+
return None;
71+
}
72+
let gc_header = (obj as *const u8).sub(crate::gc::GC_HEADER_SIZE) as *const crate::gc::GcHeader;
73+
if (*gc_header).obj_type != crate::gc::GC_TYPE_OBJECT {
74+
return None;
75+
}
76+
let key = crate::string::js_string_from_bytes(
77+
TEMPORAL_SUBCLASS_CELL_FIELD.as_ptr(),
78+
TEMPORAL_SUBCLASS_CELL_FIELD.len() as u32,
79+
);
80+
let v = js_object_get_field_by_name(obj as *const ObjectHeader, key);
81+
if v.is_undefined() {
82+
return None;
83+
}
84+
let boxed = f64::from_bits(v.bits());
85+
if crate::temporal::is_temporal_value(boxed) {
86+
Some(boxed)
87+
} else {
88+
None
89+
}
90+
}
91+
4992
/// The Web-Fetch body-reading methods (`text`/`json`/`arrayBuffer`/`blob`/
5093
/// `bytes`/`formData`/`clone`). On a `class X extends Request/Response`
5194
/// instance these live on the underlying native handle, not the JS prototype

crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,24 @@ pub extern "C" fn js_object_get_field_by_name(
400400
if let Some(v) = crate::temporal::dispatch::get_property(boxed, &name) {
401401
return JSValue::from_bits(v.to_bits());
402402
}
403+
// A prototype METHOD read as a value (`d.abs`, not `d.abs()`):
404+
// return a bound method that re-dispatches through
405+
// `js_native_call_method`. Needed because codegen lowers a
406+
// spread/dynamic call `d[m](...args)` to a property read + apply,
407+
// so the read must yield a callable. Only bind genuine method
408+
// names so an unknown property still reads as `undefined`. (#5587)
409+
if crate::temporal::dispatch::has_method(boxed, &name) {
410+
let heap_name = {
411+
let layout =
412+
std::alloc::Layout::from_size_align(key_bytes.len().max(1), 1)
413+
.unwrap();
414+
let ptr = std::alloc::alloc(layout);
415+
std::ptr::copy_nonoverlapping(key_bytes.as_ptr(), ptr, key_bytes.len());
416+
ptr
417+
};
418+
let bound = js_class_method_bind(boxed, heap_name, key_bytes.len());
419+
return JSValue::from_bits(bound.to_bits());
420+
}
403421
}
404422
}
405423
return JSValue::undefined();

crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,6 +1857,44 @@ pub(crate) fn get_field_by_name_object_tail(
18571857
}
18581858
}
18591859

1860+
// `class X extends Temporal.<Type>`: inherited accessor getters
1861+
// (`days`/`years`/`epochNanoseconds`/…) resolve via the Temporal brand on
1862+
// the underlying cell, not the JS prototype chain. Forward the read to
1863+
// the stashed cell when this object has one. Skip BOTH the temporal and
1864+
// fetch marker keys: reading a marker here would re-enter this tail and
1865+
// (cross-) trigger the other marker's reader, an infinite recursion that
1866+
// stack-overflows. Methods read as fused `inst.m(...)` calls are handled
1867+
// in `native_call_method.rs`. (#5587)
1868+
#[cfg(feature = "temporal")]
1869+
if !key.is_null()
1870+
&& key_bytes != crate::object::TEMPORAL_SUBCLASS_CELL_FIELD
1871+
&& key_bytes != FETCH_SUBCLASS_HANDLE_FIELD
1872+
{
1873+
if let Some(cell) = crate::object::temporal_subclass_cell(obj as usize) {
1874+
let name = String::from_utf8_lossy(key_bytes);
1875+
if let Some(v) = crate::temporal::dispatch::get_property(cell, &name) {
1876+
return JSValue::from_bits(v.to_bits());
1877+
}
1878+
// A prototype METHOD read as a value (`sub.abs`, not `sub.abs()`):
1879+
// return a bound method that re-dispatches through
1880+
// `js_native_call_method` (whose Temporal-subclass arm forwards to
1881+
// the cell). Only bind genuine method names so an unknown property
1882+
// still reads as `undefined`. Mirrors the fetch body-method bind.
1883+
if crate::temporal::dispatch::has_method(cell, &name) {
1884+
let this_f64 = crate::value::js_nanbox_pointer(obj as i64);
1885+
let heap_name = {
1886+
let layout =
1887+
std::alloc::Layout::from_size_align(key_bytes.len().max(1), 1).unwrap();
1888+
let ptr = std::alloc::alloc(layout);
1889+
std::ptr::copy_nonoverlapping(key_bytes.as_ptr(), ptr, key_bytes.len());
1890+
ptr
1891+
};
1892+
let bound = js_class_method_bind(this_f64, heap_name, key_bytes.len());
1893+
return JSValue::from_bits(bound.to_bits());
1894+
}
1895+
}
1896+
}
1897+
18601898
// Key not found
18611899
JSValue::undefined()
18621900
}

crates/perry-runtime/src/object/global_this.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ pub(crate) use ctor_thunks::{
8484
webcrypto_illegal_constructor_thunk, webcrypto_method_value, webcrypto_random_uuid_thunk,
8585
webcrypto_subtle_getter_thunk,
8686
};
87+
#[cfg(feature = "temporal")]
88+
pub(crate) use fetch_globals::temporal_subclass_super;
8789
pub(crate) use fetch_globals::{
8890
attach_fetch_handle_for_construction, global_this_blob_thunk, global_this_builtin_noop_thunk,
8991
global_this_date_thunk, global_this_eval_thunk, global_this_file_thunk,
@@ -113,6 +115,8 @@ pub(crate) use install_static::{
113115
};
114116
#[cfg(feature = "temporal")]
115117
pub(crate) use math_temporal::install_temporal_namespace;
118+
#[cfg(feature = "temporal")]
119+
pub(crate) use math_temporal::temporal_kind_prototype;
116120
pub(crate) use math_temporal::{install_math_namespace, temporal_ctor_kind};
117121
pub(crate) use populate::{
118122
default_prepare_stack_trace_func_ptr, populate_global_this_builtins, ERROR_CONSTRUCTOR_PTR,

crates/perry-runtime/src/object/global_this/fetch_globals.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,57 @@ unsafe fn attach_fetch_handle_to_this(this_box: f64, handle_box: f64) {
342342
}
343343
}
344344

345+
/// Stash the NaN-boxed Temporal cell (`cell_box`, what a `Temporal.<Type>`
346+
/// constructor thunk returns) on a `class X extends Temporal.<Type>` subclass
347+
/// instance's `this` under `__perry_temporal_cell__`. Stored as a real
348+
/// pointer-valued field so method/getter/instanceof dispatch can recover the
349+
/// cell (`temporal_subclass_cell`) and GC keeps it alive. (#5587)
350+
#[cfg(feature = "temporal")]
351+
unsafe fn attach_temporal_cell_to_this(this_box: f64, cell_box: f64) {
352+
if let Some(obj) = subclass_this_object_ptr(this_box) {
353+
let key = crate::string::js_string_from_bytes(
354+
crate::object::TEMPORAL_SUBCLASS_CELL_FIELD.as_ptr(),
355+
crate::object::TEMPORAL_SUBCLASS_CELL_FIELD.len() as u32,
356+
);
357+
crate::object::js_object_set_field_by_name(obj, key, cell_box);
358+
}
359+
}
360+
361+
/// `class X extends Temporal.<Type>` super-call handling, shared by the two
362+
/// `super()` lowerings: the flat-arg runtime-value dispatcher
363+
/// (`js_fetch_or_value_super`, the non-spread `super(a, b)` path) and the
364+
/// args-array `js_super_construct_apply` (the `super(...spread)` path). When
365+
/// `parent_val` is a Temporal constructor, run it (Temporal ctors return a
366+
/// fresh cell and never mutate the implicit `this`) and stash the returned cell
367+
/// on `this_box` so method / getter / instanceof dispatch can recover the
368+
/// Temporal brand. Returns `true` when handled. (#5587)
369+
#[cfg(feature = "temporal")]
370+
pub(crate) unsafe fn temporal_subclass_super(
371+
parent_val: f64,
372+
this_box: f64,
373+
args_ptr: *const f64,
374+
args_len: usize,
375+
) -> bool {
376+
if super::temporal_ctor_kind(parent_val).is_none() {
377+
return false;
378+
}
379+
// Several Temporal constructors (`PlainDate`/`PlainTime`/`Instant`/…) are
380+
// `[[Construct]]`-only and throw "requires 'new'" when `new.target` is
381+
// undefined. Invoking the parent here IS a construct, so set `new.target`
382+
// to the parent ctor for the duration of the call (the cell it returns is
383+
// re-homed onto the subclass `this`; the exact new.target identity is not
384+
// observable to these native ctors beyond being defined). Restore after.
385+
let prev_this = crate::object::js_implicit_this_set(this_box);
386+
let prev_nt = crate::object::js_new_target_set(parent_val);
387+
let cell = crate::closure::js_native_call_value(parent_val, args_ptr, args_len);
388+
crate::object::js_new_target_set(prev_nt);
389+
crate::object::js_implicit_this_set(prev_this);
390+
if crate::temporal::is_temporal_value(cell) {
391+
attach_temporal_cell_to_this(this_box, cell);
392+
}
393+
true
394+
}
395+
345396
/// Attach a native fetch handle to a freshly dynamically-constructed
346397
/// Request/Response subclass instance, building it from the `new` arguments.
347398
/// `kind` is 1 (Request) or 2 (Response). Used by the runtime
@@ -472,6 +523,33 @@ pub unsafe extern "C" fn js_fetch_or_value_super(
472523
args_len: usize,
473524
) -> f64 {
474525
let undef = f64::from_bits(crate::value::TAG_UNDEFINED);
526+
// `class X extends Temporal.<Type>` (non-spread `super(a, b)`): a Temporal
527+
// constructor returns a fresh NaN-boxed cell and does NOT mutate the
528+
// implicit `this`, so the ordinary dispatch below would drop that cell and
529+
// leave the subclass instance an empty object with no Temporal brand. Stash
530+
// the cell on `this` instead. The native ctor never calls the subclass
531+
// constructor, so `called`-counter invariants hold. (#5587)
532+
//
533+
// `parent_val` can arrive stale/undefined for an aliased heritage
534+
// (`const D = Temporal.Duration; class X extends D`) when codegen re-evaluates
535+
// the extends expression in constructor scope — exactly the case the
536+
// Request/Response branch below recovers via the decl-time stash. Mirror that:
537+
// when the immediate value isn't a Temporal ctor, fall back to the parent
538+
// value recorded against this instance's class id at declaration time.
539+
#[cfg(feature = "temporal")]
540+
{
541+
let temporal_parent = if super::temporal_ctor_kind(parent_val).is_some() {
542+
parent_val
543+
} else if let Some(obj) = subclass_this_object_ptr(this_box) {
544+
let cid = crate::object::js_object_get_class_id(obj);
545+
crate::object::class_registry::js_get_dynamic_parent_value(cid)
546+
} else {
547+
parent_val
548+
};
549+
if temporal_subclass_super(temporal_parent, this_box, args_ptr, args_len) {
550+
return undef;
551+
}
552+
}
475553
// Resolve the parent constructor kind from the value first. When the
476554
// `extends` expression is an alias of `global.Request`/`global.Response`
477555
// (`@hono/node-server`'s `class Request extends GlobalRequest`), the alias

crates/perry-runtime/src/object/global_this/math_temporal.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,49 @@ pub(crate) fn temporal_ctor_kind(_type_ref: f64) -> Option<crate::temporal::Temp
825825
None
826826
}
827827

828+
/// Resolve `Temporal.<kind>.prototype` for a Temporal value's `kind` by
829+
/// navigating the live `globalThis.Temporal.<Name>.prototype` chain (the
830+
/// prototype object is stamped on each constructor closure's `prototype`
831+
/// dynamic prop at namespace-install time). Returns `undefined` if the
832+
/// namespace isn't reachable. Used by `Object.getPrototypeOf` on a Temporal
833+
/// cell — which has no `[[Prototype]]` link of its own (it dispatches via brand
834+
/// arms) but whose reflective prototype IS `Temporal.<Type>.prototype`. (#5587)
835+
#[cfg(feature = "temporal")]
836+
pub(crate) fn temporal_kind_prototype(kind: crate::temporal::TemporalKind) -> f64 {
837+
use crate::temporal::TemporalKind::*;
838+
let undef = f64::from_bits(crate::value::TAG_UNDEFINED);
839+
let name: &[u8] = match kind {
840+
Duration => b"Duration",
841+
Instant => b"Instant",
842+
PlainDate => b"PlainDate",
843+
PlainTime => b"PlainTime",
844+
PlainDateTime => b"PlainDateTime",
845+
PlainYearMonth => b"PlainYearMonth",
846+
PlainMonthDay => b"PlainMonthDay",
847+
ZonedDateTime => b"ZonedDateTime",
848+
};
849+
let g = super::js_get_global_this();
850+
let gp = (g.to_bits() & crate::value::POINTER_MASK) as *const ObjectHeader;
851+
if gp.is_null() {
852+
return undef;
853+
}
854+
let tkey = crate::string::js_string_from_bytes(b"Temporal".as_ptr(), 8);
855+
let temporal = js_object_get_field_by_name(gp, tkey);
856+
if !temporal.is_pointer() {
857+
return undef;
858+
}
859+
let tp = (temporal.bits() & crate::value::POINTER_MASK) as *const ObjectHeader;
860+
let ckey = crate::string::js_string_from_bytes(name.as_ptr(), name.len() as u32);
861+
let ctor = js_object_get_field_by_name(tp, ckey);
862+
if !ctor.is_pointer() {
863+
return undef;
864+
}
865+
let cp = (ctor.bits() & crate::value::POINTER_MASK) as *const ObjectHeader;
866+
let pkey = crate::string::js_string_from_bytes(b"prototype".as_ptr(), 9);
867+
let proto = js_object_get_field_by_name(cp, pkey);
868+
f64::from_bits(proto.bits())
869+
}
870+
828871
/// `Temporal.PlainDate.prototype` accessor getters and method shapes (#4691).
829872
#[cfg(feature = "temporal")]
830873
const PLAIN_DATE_GETTERS: &[&str] = &[

crates/perry-runtime/src/object/instanceof.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,35 @@ pub extern "C" fn js_instanceof_dynamic(value: f64, type_ref: f64) -> f64 {
109109
// its kind and compare against the value's brand. A non-Temporal value, or
110110
// a Temporal value of a different kind, yields `false`.
111111
if let Some(kind) = super::global_this::temporal_ctor_kind(type_ref) {
112-
return if crate::temporal::temporal_kind(value) == Some(kind) {
113-
f64::from_bits(crate::value::TAG_TRUE)
114-
} else {
115-
f64::from_bits(TAG_FALSE)
116-
};
112+
if crate::temporal::temporal_kind(value) == Some(kind) {
113+
return f64::from_bits(crate::value::TAG_TRUE);
114+
}
115+
// `class X extends Temporal.<Type>` instance: a plain heap object whose
116+
// [[Prototype]] chain reaches `Temporal.<Type>.prototype`. It carries
117+
// the brand via a stashed cell rather than the Temporal-cell tag, so
118+
// recover that cell and compare its kind. The receiver reaches here both
119+
// NaN-boxed (top16 == 0x7FFD) and as a raw-I64 heap pointer (top16 == 0,
120+
// how module-level object vars are stored) — accept both. (#5587)
121+
#[cfg(feature = "temporal")]
122+
{
123+
let bits = value.to_bits();
124+
let top16 = bits >> 48;
125+
let raw = if top16 == 0x7FFD {
126+
(bits & crate::value::POINTER_MASK) as usize
127+
} else if top16 == 0 {
128+
bits as usize
129+
} else {
130+
0
131+
};
132+
if raw != 0 {
133+
if let Some(cell) = unsafe { crate::object::temporal_subclass_cell(raw) } {
134+
if crate::temporal::temporal_kind(cell) == Some(kind) {
135+
return f64::from_bits(crate::value::TAG_TRUE);
136+
}
137+
}
138+
}
139+
}
140+
return f64::from_bits(TAG_FALSE);
117141
}
118142
let bits = type_ref.to_bits();
119143
let top16 = bits >> 48;

crates/perry-runtime/src/object/native_call_method.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,21 @@ pub unsafe extern "C" fn js_native_call_method(
13291329
}
13301330
}
13311331

1332+
// `class X extends Temporal.<Type>`: the prototype methods (`add`/`abs`/
1333+
// `toString`/…) dispatch via the Temporal brand on the underlying cell, not
1334+
// the JS prototype chain. All user-defined dispatch (own fields, vtable,
1335+
// prototype walk) has missed by here, so a subclass override still wins;
1336+
// only genuinely inherited Temporal methods reach this forward. Route them
1337+
// to the stashed cell (`temporal_subclass_cell`). (#5587)
1338+
#[cfg(feature = "temporal")]
1339+
if jsval.is_pointer() {
1340+
let raw = crate::value::js_nanbox_get_pointer(object) as usize;
1341+
if let Some(cell) = crate::object::temporal_subclass_cell(raw) {
1342+
let args = refreshed_args();
1343+
return crate::temporal::dispatch::call_method(cell, method_name, &args);
1344+
}
1345+
}
1346+
13321347
// #4973: inherits-pattern instances (`http.Server.call(this, …)`) forward
13331348
// method calls that missed every user-defined dispatch layer (own fields,
13341349
// vtable, prototype walk) to their aliased native handle, so

0 commit comments

Comments
 (0)