diff --git a/clusterAg b/clusterAg new file mode 100755 index 000000000..071f461c9 Binary files /dev/null and b/clusterAg differ diff --git a/crates/perry-hir/src/lower/lower_module_fn.rs b/crates/perry-hir/src/lower/lower_module_fn.rs index 137e4f9f8..e2860b416 100644 --- a/crates/perry-hir/src/lower/lower_module_fn.rs +++ b/crates/perry-hir/src/lower/lower_module_fn.rs @@ -667,20 +667,37 @@ pub fn lower_module_full( names.dedup(); for name in names { // Reuse an existing global `var`, else mint a fresh hoisted slot; - // either way emit an undefined-init entry slot so the block's B.3.3 - // write (which runs before any source-position `var f = …`) has - // storage to target. + // either way emit an entry slot so the block's B.3.3 write (which + // runs before any source-position `var f = …`) has storage to target. let id = if let Some(existing) = ctx.lookup_local(&name) { existing } else { ctx.define_local(name.clone(), Type::Any) }; + // B.3.3 entry value. Normally the legacy `var` is `undefined` until + // the block declaration runs. But when a same-named *top-level* + // function declaration also exists, F is already in + // declaredFunctionNames, so B.3.3 does NOT create a fresh + // `undefined` binding — the function declaration owns the entry + // value. A non-reassigned top-level `function f` is otherwise called + // straight through `lookup_func` and never bound to this var slot, + // so without this the legacy var shadows it as `undefined` and + // `f()` throws at entry (the `existing-fn-no-init` cluster, #5346). + // Seed the slot with the function and mark it function-valued; the + // block-level declaration still overwrites it (`existing-fn-update`). + let init = match ctx.lookup_func(&name) { + Some(func_id) if functions_with_bodies.contains(&name) => { + ctx.function_valued_locals.insert(id); + Expr::FuncRef(func_id) + } + _ => Expr::Undefined, + }; module.init.push(Stmt::Let { id, name: name.clone(), ty: Type::Any, mutable: true, - init: Some(Expr::Undefined), + init: Some(init), }); ctx.var_hoisted_ids.insert(id); ctx.annexb_block_fn_var_ids.insert(name, id); diff --git a/crates/perry-runtime/src/object/date_proto_thunks.rs b/crates/perry-runtime/src/object/date_proto_thunks.rs index 67b7933b1..da6891e7d 100644 --- a/crates/perry-runtime/src/object/date_proto_thunks.rs +++ b/crates/perry-runtime/src/object/date_proto_thunks.rs @@ -299,18 +299,17 @@ pub(crate) fn install_date_proto_getters(proto_obj: *mut ObjectHeader) { 0, ); super::global_this::install_proto_method(proto_obj, "toJSON", date_to_json as *const u8, 1); - super::global_this::install_proto_method( + let utc = super::global_this::install_proto_method( proto_obj, "toUTCString", date_to_utc_string as *const u8, 0, ); - super::global_this::install_proto_method( - proto_obj, - "toGMTString", - date_to_utc_string as *const u8, - 0, - ); + // Annex B: `Date.prototype.toGMTString` is the SAME function object as + // `toUTCString` (`toGMTString === toUTCString`, `.name === "toUTCString"`), + // not an independent thunk (test262 `annexB/built-ins/Date/.../toGMTString/ + // value`, #5346). + super::global_this::install_proto_method_alias(proto_obj, "toGMTString", utc); } // --- Setters --------------------------------------------------------------- diff --git a/crates/perry-runtime/src/object/global_this.rs b/crates/perry-runtime/src/object/global_this.rs index 69b398c98..d351227b3 100644 --- a/crates/perry-runtime/src/object/global_this.rs +++ b/crates/perry-runtime/src/object/global_this.rs @@ -6649,6 +6649,28 @@ pub(super) fn install_proto_method( value } +/// Install `alias_name` on `proto_obj` as the SAME function object as an +/// already-installed method (`value` is that method's installed property +/// value). Annex B legacy aliases — `trimLeft`→`trimStart`, +/// `trimRight`→`trimEnd`, `toGMTString`→`toUTCString` — are required to be the +/// very same function object (`String.prototype.trimLeft === trimStart`, and +/// `.name` reports the canonical method's name), with the standard +/// `{ writable: true, enumerable: false, configurable: true }` method +/// descriptor. See test262 `annexB/built-ins/{String,Date}` (#5346). +pub(super) fn install_proto_method_alias( + proto_obj: *mut ObjectHeader, + alias_name: &str, + value: f64, +) { + let key = crate::string::js_string_from_bytes(alias_name.as_ptr(), alias_name.len() as u32); + js_object_set_field_by_name(proto_obj, key, value); + super::set_builtin_property_attrs( + proto_obj as usize, + alias_name.to_string(), + super::PropertyAttrs::new(true, false, true), + ); +} + pub(super) fn install_proto_method_rest( proto_obj: *mut ObjectHeader, method_name: &str, diff --git a/crates/perry-runtime/src/object/string_proto_thunks.rs b/crates/perry-runtime/src/object/string_proto_thunks.rs index 7032cb020..71f7933d9 100644 --- a/crates/perry-runtime/src/object/string_proto_thunks.rs +++ b/crates/perry-runtime/src/object/string_proto_thunks.rs @@ -122,6 +122,19 @@ fn install_generic_string_proto_methods(proto_obj: *mut ObjectHeader) { 0, ); } + // Annex B: `trimLeft`/`trimRight` are the SAME function objects as + // `trimStart`/`trimEnd` — `String.prototype.trimLeft === trimStart` and + // `String.prototype.trimLeft.name === "trimStart"` — so alias the already- + // installed property value rather than installing a second thunk (whose + // closure would carry the name `trimLeft` and re-dispatch correctly but + // fail the reference-equality and `.name` checks). The generic thunk reads + // its own name off the closure, so the aliased call still trims the right + // side. test262 `annexB/built-ins/String/.../trim{Left,Right}` (#5346). + for (alias, canonical) in [("trimLeft", "trimStart"), ("trimRight", "trimEnd")] { + let key = crate::string::js_string_from_bytes(canonical.as_ptr(), canonical.len() as u32); + let value = super::js_object_get_field_by_name_f64(proto_obj, key); + super::global_this::install_proto_method_alias(proto_obj, alias, value); + } } /// Generic `String.prototype` method thunk. Performs `RequireObjectCoercible(