Skip to content

Commit 9653ae1

Browse files
ssrliveCopilot
andcommitted
Add RequireObjectCoercible + ToString coercion to String prototype methods
String.prototype methods now properly: - Throw TypeError when called on null/undefined - Coerce non-string primitives (Number, Boolean, BigInt) via ToString - Coerce generic objects via JS-level ToString (calls toString/valueOf) Only applies to string method builtin IDs (110-134) to avoid affecting other native functions. Cluster 37 (String): 540 → 394 failures (146 tests fixed) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a42831b commit 9653ae1

File tree

1 file changed

+32
-13
lines changed

1 file changed

+32
-13
lines changed

src/core/vm.rs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7699,19 +7699,20 @@ impl<'gc> VM<'gc> {
76997699
let mut i = 0;
77007700
while i < len {
77017701
if bytes[i] == b'%' {
7702-
if i + 5 < len && bytes[i + 1] == b'u' {
7703-
if let Ok(hex) = u16::from_str_radix(std::str::from_utf8(&bytes[i + 2..i + 6]).unwrap_or(""), 16) {
7704-
result.push(hex);
7705-
i += 6;
7706-
continue;
7707-
}
7702+
if i + 5 < len
7703+
&& bytes[i + 1] == b'u'
7704+
&& let Ok(hex) = u16::from_str_radix(std::str::from_utf8(&bytes[i + 2..i + 6]).unwrap_or(""), 16)
7705+
{
7706+
result.push(hex);
7707+
i += 6;
7708+
continue;
77087709
}
7709-
if i + 2 < len {
7710-
if let Ok(hex) = u8::from_str_radix(std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or(""), 16) {
7711-
result.push(hex as u16);
7712-
i += 3;
7713-
continue;
7714-
}
7710+
if i + 2 < len
7711+
&& let Ok(hex) = u8::from_str_radix(std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or(""), 16)
7712+
{
7713+
result.push(hex as u16);
7714+
i += 3;
7715+
continue;
77157716
}
77167717
}
77177718
result.push(bytes[i] as u16);
@@ -28990,7 +28991,8 @@ impl<'gc> VM<'gc> {
2899028991
}
2899128992
}
2899228993

28993-
// String methods
28994+
// String methods — RequireObjectCoercible(this) + ToString(this)
28995+
let is_string_method = (BUILTIN_STRING_SPLIT..=BUILTIN_STRING_VALUEOF).contains(&id);
2899428996
let string_val: Option<Vec<u16>> = match &receiver {
2899528997
Value::String(s) => Some(s.clone()),
2899628998
Value::VmObject(map) => {
@@ -29000,10 +29002,27 @@ impl<'gc> VM<'gc> {
2900029002
Some(Value::String(s)) => Some(s.clone()),
2900129003
_ => None,
2900229004
}
29005+
} else if is_string_method {
29006+
// Generic object — ToString via vm_to_string (calls JS toString/valueOf)
29007+
drop(b);
29008+
let s = self.vm_to_string(ctx, receiver);
29009+
if self.pending_throw.is_some() {
29010+
return Value::Undefined;
29011+
}
29012+
Some(crate::unicode::utf8_to_utf16(&s))
2900329013
} else {
2900429014
None
2900529015
}
2900629016
}
29017+
Value::Undefined | Value::Null if is_string_method => {
29018+
self.throw_type_error(ctx, "String.prototype method called on null or undefined");
29019+
return Value::Undefined;
29020+
}
29021+
// Number, Boolean, BigInt — coerce via ToString for string methods
29022+
other if is_string_method && !matches!(other, Value::Undefined | Value::Null) => {
29023+
let s = value_to_string(other);
29024+
Some(crate::unicode::utf8_to_utf16(&s))
29025+
}
2900729026
_ => None,
2900829027
};
2900929028
if let Some(ref s) = string_val {

0 commit comments

Comments
 (0)