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
Binary file added clusterAg
Binary file not shown.
25 changes: 21 additions & 4 deletions crates/perry-hir/src/lower/lower_module_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 6 additions & 7 deletions crates/perry-runtime/src/object/date_proto_thunks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ---------------------------------------------------------------
Expand Down
22 changes: 22 additions & 0 deletions crates/perry-runtime/src/object/global_this.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions crates/perry-runtime/src/object/string_proto_thunks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading