From e7b24f950855eeef6b3f75a360252b94b6629194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 24 Aug 2026 21:26:57 +0200 Subject: [PATCH] fix(runtime): inherit Object prototype on class instances Lands #8780. A registered ES class instance whose class methods and accessors all missed was excluded from the ordinary Object.prototype read fallback, so inherited reads -- `String(new C())` among them -- failed. Declared class instances do end their implicit prototype chain at Object.prototype, and their own methods are consulted before this fallback runs, so a miss must stay eligible. The exclusion now also requires the class id to be unregistered, keeping native and synthetic class ids on their existing intrinsic prototype paths. No version bump. --- .../8780-class-instance-object-prototype-read.md | 4 ++++ .../src/object/field_get_set/accessors.rs | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 changelog.d/8780-class-instance-object-prototype-read.md diff --git a/changelog.d/8780-class-instance-object-prototype-read.md b/changelog.d/8780-class-instance-object-prototype-read.md new file mode 100644 index 0000000000..87a3f5c314 --- /dev/null +++ b/changelog.d/8780-class-instance-object-prototype-read.md @@ -0,0 +1,4 @@ +Fixed inherited `Object.prototype` property reads on declared class instances. +Classes without their own `toString` or `valueOf` now resolve the default +methods instead of reporting the properties as present while reading them as +`undefined`, restoring ordinary string coercion such as `String(new C())`. diff --git a/crates/perry-runtime/src/object/field_get_set/accessors.rs b/crates/perry-runtime/src/object/field_get_set/accessors.rs index de75ce90db..5f5059da14 100644 --- a/crates/perry-runtime/src/object/field_get_set/accessors.rs +++ b/crates/perry-runtime/src/object/field_get_set/accessors.rs @@ -240,7 +240,16 @@ pub(crate) unsafe fn ordinary_object_prototype_property_value( return None; } let class_id = (*obj).class_id; - if class_id != 0 && !is_anon_shape_class_id(class_id) { + // Declared ES class instances have a registered class id, but still end + // their implicit prototype chain at Object.prototype. Their class + // methods/accessors have already been consulted before this fallback, so + // a miss must remain eligible for Object.prototype (including user-added + // properties). Keep excluding unregistered native/synthetic class ids: + // those object kinds resolve their own intrinsic prototype chains. + if class_id != 0 + && !is_anon_shape_class_id(class_id) + && !super::super::class_registry::is_class_id_registered(class_id) + { return None; } default_object_prototype_property_value(obj as usize, key)