Skip to content

Commit 807ee49

Browse files
committed
fix: implement %ThrowTypeError% intrinsic per spec (14/14 Test262 pass)
- Create proper %ThrowTypeError% object per realm: closure-backed, non-extensible, length=0 and name='' both non-configurable/non-writable/non-enumerable - Fix delete handler: non-configurable length/name on closures no longer force-deleted - Fix cross-realm TypeError identity: restrictedThrow uses function's own realm - Add OriginGlobal slot on ThrowTypeError for get_function_realm - Fix Function constructor: propagate body_is_strict to closure_data.is_strict - Strict arguments callee uses ThrowTypeError accessor (getter+setter)
1 parent a1a39b0 commit 807ee49

5 files changed

Lines changed: 63 additions & 41 deletions

File tree

ci/feature_probes/Temporal.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// feature probe for 'Temporal'
2+
if (typeof Temporal === 'undefined') throw new Error('Temporal not supported');
3+
console.log('OK');
4+

src/core/eval.rs

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17135,21 +17135,10 @@ fn evaluate_expr_delete<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'g
1713517135
}
1713617136

1713717137
if obj.borrow().non_configurable.contains(&key_val) {
17138-
let is_function_like = obj.borrow().get_closure().is_some();
17139-
if is_function_like && (key == "length" || key == "name") {
17140-
if let Some(cl_ptr) = obj.borrow().get_closure()
17141-
&& let Value::Function(func_name) = &*cl_ptr.borrow()
17142-
{
17143-
mark_builtin_function_virtual_prop_deleted(mc, env, func_name, key)?;
17144-
}
17145-
let mut o = obj.borrow_mut(mc);
17146-
let _ = o.properties.shift_remove(&key_val);
17147-
let _ = o.non_configurable.remove(&key_val);
17148-
let _ = o.non_writable.remove(&key_val);
17149-
let _ = o.non_enumerable.remove(&key_val);
17150-
Ok(Value::Boolean(true))
17151-
} else {
17138+
if env_get_strictness(env) {
1715217139
Err(crate::raise_type_error!(format!("Cannot delete non-configurable property '{key}'",)).into())
17140+
} else {
17141+
Ok(Value::Boolean(false))
1715317142
}
1715417143
} else {
1715517144
let _ = obj.borrow_mut(mc).properties.shift_remove(&key_val);
@@ -17225,27 +17214,14 @@ fn evaluate_expr_delete<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'g
1722517214
return Ok(Value::Boolean(true));
1722617215
}
1722717216
if obj.borrow().non_configurable.contains(&key) {
17228-
let is_fn_length_or_name = matches!(&key, PropertyKey::String(s) if s == "length" || s == "name");
17229-
let is_function_like = obj.borrow().get_closure().is_some();
17230-
if is_function_like && is_fn_length_or_name {
17231-
if let PropertyKey::String(s) = &key
17232-
&& let Some(cl_ptr) = obj.borrow().get_closure()
17233-
&& let Value::Function(func_name) = &*cl_ptr.borrow()
17234-
{
17235-
mark_builtin_function_virtual_prop_deleted(mc, env, func_name, s)?;
17236-
}
17237-
let mut o = obj.borrow_mut(mc);
17238-
let _ = o.properties.shift_remove(&key);
17239-
let _ = o.non_configurable.remove(&key);
17240-
let _ = o.non_writable.remove(&key);
17241-
let _ = o.non_enumerable.remove(&key);
17242-
Ok(Value::Boolean(true))
17243-
} else {
17217+
if env_get_strictness(env) {
1724417218
Err(crate::raise_type_error!(format!(
1724517219
"Cannot delete non-configurable property '{}'",
1724617220
value_to_string(&key_val_res)
1724717221
))
1724817222
.into())
17223+
} else {
17224+
Ok(Value::Boolean(false))
1724917225
}
1725017226
} else {
1725117227
let _ = obj.borrow_mut(mc).properties.shift_remove(&key);
@@ -19083,7 +19059,13 @@ pub fn call_native_function<'gc>(
1908319059

1908419060
// Restricted accessor used to implement the throwing 'caller'/'arguments' accessors on Function.prototype
1908519061
if name == "Function.prototype.restrictedThrow" {
19086-
return Err(raise_type_error!("Access to 'caller' or 'arguments' is restricted").into());
19062+
// Use the env passed to this function (which is the ThrowTypeError's own realm
19063+
// when called cross-realm) so the TypeError is instanceof that realm's TypeError.
19064+
return Err(throw_realm_type_error(
19065+
mc,
19066+
env,
19067+
"'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them",
19068+
));
1908719069
}
1908819070

1908919071
if name == "AsyncGenerator.prototype.next" {
@@ -19850,7 +19832,11 @@ pub(crate) fn call_accessor<'gc>(
1985019832
Value::Function(name) => {
1985119833
// Special-case restricted accessor thrower
1985219834
if name == "Function.prototype.restrictedThrow" {
19853-
return Err(raise_type_error!("Access to 'caller' or 'arguments' is restricted").into());
19835+
return Err(throw_realm_type_error(
19836+
mc,
19837+
env,
19838+
"'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them",
19839+
));
1985419840
}
1985519841
if let Some(res) = call_native_function(mc, name, Some(&Value::Object(*receiver)), &[], env)? {
1985619842
Ok(res)
@@ -19864,7 +19850,11 @@ pub(crate) fn call_accessor<'gc>(
1986419850
Ok(v) => Ok(v),
1986519851
Err(_) => {
1986619852
if name.contains("restrictedThrow") {
19867-
Err(raise_type_error!("Access to 'caller' or 'arguments' is restricted").into())
19853+
Err(throw_realm_type_error(
19854+
mc,
19855+
env,
19856+
"'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them",
19857+
))
1986819858
} else {
1986919859
Err(raise_type_error!(format!("Accessor function {name} not supported")).into())
1987019860
}

src/core/value.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@ pub enum InternalSlot {
503503
SuppressDynamicImportResult, // __suppress_dynamic_import_result
504504
SymbolRegistry, // __symbol_registry
505505
TemplateRegistry, // __template_registry
506+
ThrowTypeError, // __throw_type_error
506507

507508
// --- Misc ---
508509
Eof, // __eof
@@ -680,6 +681,7 @@ pub fn str_to_internal_slot(s: &str) -> Option<InternalSlot> {
680681
"__suppress_dynamic_import_result" => return Some(InternalSlot::SuppressDynamicImportResult),
681682
"__symbol_registry" => return Some(InternalSlot::SymbolRegistry),
682683
"__template_registry" => return Some(InternalSlot::TemplateRegistry),
684+
"__throw_type_error" => return Some(InternalSlot::ThrowTypeError),
683685
// Misc
684686
"__eof" => return Some(InternalSlot::Eof),
685687
"__lookupGetter__" => return Some(InternalSlot::LookupGetter),

src/js_class.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,12 @@ pub fn create_arguments_object<'gc>(
687687

688688
if let Some(callee_val) = callee {
689689
if crate::core::env_get_strictness(func_env) {
690-
let thrower_val = Value::Function("Function.prototype.restrictedThrow".to_string());
690+
// Use the realm's %ThrowTypeError% intrinsic if available
691+
let thrower_val = if let Some(tte_rc) = crate::core::slot_get_chained(func_env, &crate::core::InternalSlot::ThrowTypeError) {
692+
tte_rc.borrow().clone()
693+
} else {
694+
Value::Function("Function.prototype.restrictedThrow".to_string())
695+
};
691696

692697
let prop = crate::core::Value::Property {
693698
value: None,

src/js_function.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,6 +1474,8 @@ fn function_constructor<'gc>(
14741474
let mut closure_data = ClosureData::new(params, body, Some(global_env), None);
14751475
// Function constructor created functions should not inherit strict mode from the context
14761476
closure_data.enforce_strictness_inheritance = false;
1477+
// Propagate "use strict" directive from the body to the closure
1478+
closure_data.is_strict = body_is_strict;
14771479
let closure_val = Value::Closure(Gc::new(mc, closure_data));
14781480

14791481
// Create a function object wrapper so it has a proper `prototype` and property attributes
@@ -3691,14 +3693,33 @@ pub fn initialize_function<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr
36913693
let proto_name_desc = crate::core::create_descriptor_object(mc, &Value::String(utf8_to_utf16("")), false, false, true)?;
36923694
crate::js_object::define_property_internal(mc, &func_proto, "name", &proto_name_desc)?;
36933695

3694-
// Define restricted 'caller' and 'arguments' accessors that throw a TypeError when accessed or assigned
3696+
// §10.2.4 Create %ThrowTypeError% intrinsic — a unique frozen function object per realm.
3697+
let throw_type_error = crate::core::new_js_object_data(mc);
3698+
throw_type_error.borrow_mut(mc).set_closure(Some(crate::core::new_gc_cell_ptr(
3699+
mc,
3700+
Value::Function("Function.prototype.restrictedThrow".to_string()),
3701+
)));
3702+
// Set [[Prototype]] to Function.prototype
3703+
throw_type_error.borrow_mut(mc).prototype = Some(func_proto);
3704+
// Stamp OriginGlobal so get_function_realm returns this realm's env
3705+
// (critical for cross-realm ThrowTypeError to throw the correct realm's TypeError).
3706+
crate::core::slot_set(mc, &throw_type_error, crate::core::InternalSlot::OriginGlobal, &Value::Object(*env));
3707+
// length = 0: { writable: false, enumerable: false, configurable: false }
3708+
let tte_len_desc = crate::core::create_descriptor_object(mc, &Value::Number(0.0), false, false, false)?;
3709+
crate::js_object::define_property_internal(mc, &throw_type_error, "length", &tte_len_desc)?;
3710+
// name = "": { writable: false, enumerable: false, configurable: false }
3711+
let tte_name_desc = crate::core::create_descriptor_object(mc, &Value::String(utf8_to_utf16("")), false, false, false)?;
3712+
crate::js_object::define_property_internal(mc, &throw_type_error, "name", &tte_name_desc)?;
3713+
// Make non-extensible
3714+
throw_type_error.borrow_mut(mc).prevent_extensions();
3715+
// Store on env for reuse (arguments.callee in strict mode, etc.)
3716+
crate::core::slot_set(mc, env, crate::core::InternalSlot::ThrowTypeError, &Value::Object(throw_type_error));
3717+
3718+
// Define restricted 'caller' and 'arguments' accessors using %ThrowTypeError%
3719+
let thrower_val = Value::Object(throw_type_error);
36953720
let restricted_desc = crate::core::new_js_object_data(mc);
3696-
let val = Value::Function("Function.prototype.restrictedThrow".to_string());
3697-
object_set_key_value(mc, &restricted_desc, "get", &val)?;
3698-
3699-
let val = Value::Function("Function.prototype.restrictedThrow".to_string());
3700-
object_set_key_value(mc, &restricted_desc, "set", &val)?;
3701-
3721+
object_set_key_value(mc, &restricted_desc, "get", &thrower_val)?;
3722+
object_set_key_value(mc, &restricted_desc, "set", &thrower_val)?;
37023723
object_set_key_value(mc, &restricted_desc, "configurable", &Value::Boolean(true))?;
37033724
crate::js_object::define_property_internal(mc, &func_proto, "caller", &restricted_desc)?;
37043725
crate::js_object::define_property_internal(mc, &func_proto, "arguments", &restricted_desc)?;

0 commit comments

Comments
 (0)