Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog.d/8768-runtime-parent-function-prototype.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fixed subclasses of runtime-valued ordinary functions failing during prototype
materialization when the parent function's lazy `.prototype` had not already
been read. `class Child extends Parent` now observes and links the same
prototype object as an ordinary `Parent.prototype` read.
30 changes: 8 additions & 22 deletions crates/perry-runtime/src/object/class_registry/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -750,30 +750,16 @@ pub(crate) fn class_decl_prototype_value(class_id: u32) -> f64 {
if parent.is_pointer() {
let parent_addr = parent.as_pointer::<u8>() as usize;
if crate::closure::is_closure_ptr(parent_addr) {
// Use the same observable `.prototype` read as ordinary
// property access. Plain functions and bound native-module
// constructor exports materialize this object lazily, while
// explicit, deleted, and generator prototypes must retain
// their own semantics.
let parent_proto =
crate::closure::closure_get_dynamic_prop(parent_addr, "prototype");
// A bound native-module constructor export imported directly
// (`import { EventEmitter } from "events"; class X extends
// EventEmitter {}`) carries `.prototype` only lazily: the raw
// dynamic-slot read above is still `undefined` because the
// synthetic prototype object (which carries the EventEmitter
// method surface) is materialized on demand, not at closure
// mint time. Resolve it exactly as an ordinary `Y.prototype`
// property read does, so the `extends` edge links to that real
// prototype instead of throwing. `Stream` and the net/http
// server classes share this shape. A non-constructor bound
// method still resolves to `None` here, so a genuinely
// prototype-less parent (e.g. a bare `fn.bind(...)`) still
// throws below, matching Node.
let resolved_proto = if class_parent_prototype_bits(parent_proto).is_some() {
parent_proto
} else {
super::function_prototype::ordinary_function_prototype_value_for_read(
super::function_prototype::js_function_prototype_value_for_read(
dynamic_parent.get_nanbox_f64(),
)
.unwrap_or(parent_proto)
};
if let Some(bits) = class_parent_prototype_bits(resolved_proto) {
);
if let Some(bits) = class_parent_prototype_bits(parent_proto) {
Some(bits)
} else {
super::super::object_ops::throw_object_type_error(
Expand Down
Loading