Skip to content
Merged
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
29 changes: 29 additions & 0 deletions crates/perry-hir/src/lower/shared_mutable_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,35 @@ fn rewrite_expr(expr: &mut Expr, shared: &HashSet<LocalId>, index_uses: &HashSet
// Capture sites snapshot the WHOLE handle (the array). Leave the bare
// `LocalGet(id)` capture args alone; still rewrite non-capture children.
Expr::RegisterClassCaptures { .. } => return,
// #6497: the per-evaluation fresh-binding path (#6470) carries the
// same capture-param-ordered `LocalGet` args as RegisterClassCaptures
// — they too must snapshot the WHOLE box handle. Rewriting them to
// `id[0]` stored the cell's current VALUE on the heap class object,
// so methods of a class that captures AND mutates a local indexed
// into a number: reads came back `undefined` and writes were lost
// (gap tests anon_shape_boxed_capture / 5952_mixin_factory_binding).
// Statics' initializer values are ordinary reads and still rewrite.
Expr::ClassExprFresh {
named_statics,
symbol_statics,
captured_args,
..
} => {
for (_, v) in named_statics.iter_mut() {
rewrite_expr(v, shared, index_uses);
}
for (k, v) in symbol_statics.iter_mut() {
rewrite_expr(k, shared, index_uses);
rewrite_expr(v, shared, index_uses);
}
for a in captured_args.iter_mut() {
if matches!(a, Expr::LocalGet(id) if index_uses.contains(id)) {
continue; // capture argument — keep the array handle
}
rewrite_expr(a, shared, index_uses);
}
return;
}
Expr::New {
class_name, args, ..
} => {
Expand Down
1 change: 1 addition & 0 deletions crates/perry-runtime/src/object/field_get_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ pub(crate) fn is_fetch_subclass_body_method(name: &[u8]) -> bool {
// ── Topical sub-modules (issue #1103: keep every file < 2000 lines) ──
mod accessors;
mod buffer_own_prop;
mod class_object_props;
mod crypto_key;
mod enumeration;
mod field_ops;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//! #6497: `.name` on a heap class-expression value (`ClassExprFresh` — #6470
//! also routes capturing function-body class DECLARATIONS through it) must
//! expose the template's registry name, matching the INT32 class-ref path's
//! #2059 arm. Split from `get_field_by_name_tail.rs` for the file-size cap.

use super::*;

/// #4949: heap class-expression values (`ClassExprFresh`) are real
/// OBJECT_TYPE_CLASS objects, not INT32 class refs. Their `.prototype`
/// read must still expose the live declared-class prototype object so
/// tsc/tslib decorator code can inspect and mutate method descriptors.
pub(super) unsafe fn class_object_prototype_value(obj: *const ObjectHeader) -> JSValue {
let class_id = (*obj).class_id;
let value = super::super::class_registry::class_decl_prototype_value(class_id);
if value.to_bits() == crate::value::TAG_UNDEFINED {
let value = super::super::class_prototype_ref_value(class_id);
return JSValue::from_bits(value.to_bits());
}
JSValue::from_bits(value.to_bits())
}

/// Resolve `.name` for an `OBJECT_TYPE_CLASS` heap object. An explicit
/// `static name` member (an own field on the class object) wins; a deleted
/// key still reads `undefined` (returns `None`).
pub(super) unsafe fn class_object_name_value(
obj: *const ObjectHeader,
key: *const crate::StringHeader,
) -> Option<JSValue> {
if let Some(v) = own_data_field_by_name(obj, key) {
return Some(v);
}
let class_id = (*obj).class_id;
if super::super::class_registry::class_is_key_deleted(class_id, "name") {
return None;
}
let cname = super::super::class_registry::class_name_for_id(class_id)?;
let s = crate::string::js_string_from_bytes(cname.as_ptr(), cname.len() as u32);
Some(JSValue::from_bits(
crate::js_nanbox_string(s as i64).to_bits(),
))
}
Original file line number Diff line number Diff line change
Expand Up @@ -1312,21 +1312,17 @@ pub(crate) fn get_field_by_name_object_tail(
let key_ptr = (key as *const u8).add(std::mem::size_of::<crate::StringHeader>());
let key_len = (*key).byte_len as usize;
let key_bytes = std::slice::from_raw_parts(key_ptr, key_len);
// #4949: heap class-expression values (`ClassExprFresh`) are real
// OBJECT_TYPE_CLASS objects, not INT32 class refs. Their `.prototype`
// read must still expose the live declared-class prototype object so
// tsc/tslib decorator code can inspect and mutate method descriptors.
if key_bytes == b"prototype"
&& (*obj).object_type == crate::error::OBJECT_TYPE_CLASS
&& (*obj).class_id != 0
{
let class_id = (*obj).class_id;
let value = super::super::class_registry::class_decl_prototype_value(class_id);
if value.to_bits() == crate::value::TAG_UNDEFINED {
let value = super::super::class_prototype_ref_value(class_id);
return JSValue::from_bits(value.to_bits());
// #4949 `.prototype` / #6497 `.name` on heap class-expression
// values — see `class_object_props`.
if (*obj).object_type == crate::error::OBJECT_TYPE_CLASS && (*obj).class_id != 0 {
if key_bytes == b"prototype" {
return super::class_object_props::class_object_prototype_value(obj);
}
if key_bytes == b"name" {
if let Some(v) = super::class_object_props::class_object_name_value(obj, key) {
return v;
}
}
return JSValue::from_bits(value.to_bits());
}
if (*obj).class_id == CLASS_ID_BOXED_STRING {
if let Some((_, payload)) = crate::builtins::boxed_primitive_payload(
Expand Down
Loading