Skip to content

Commit 0c723d6

Browse files
committed
Implement WeakRef, FinalizationRegistry, and Atomics.waitAsync
- Add WeakRef constructor and prototype.deref() in new js_weakref.rs - Add FinalizationRegistry constructor, register, unregister, cleanupSome in new js_finalization_registry.rs - Add Atomics.waitAsync with background thread waiter and promise resolution in js_typedarray.rs - Define InternalSlot variants for WeakRef and FinalizationRegistry in value.rs - Register new builtins in global initialization (mod.rs, lib.rs) - Wire constructor dispatch in js_class.rs and eval.rs for new/Reflect.construct - Fix parser to allow keyword tokens (e.g. async) as destructuring property keys - Fix parser async lookahead in object literals so { async: true } is not parsed as async method - Fix FinalizationRegistry.prototype.register.length (2) and unregister.length (1) across dot-access, bracket-access, and getOwnPropertyDescriptor paths - Add FinalizationRegistry.js feature probe for test262 runner - Add manual test scripts for WeakRef, FinalizationRegistry, and Atomics.waitAsync
1 parent ecb7580 commit 0c723d6

File tree

14 files changed

+1087
-3
lines changed

14 files changed

+1087
-3
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// feature probe for 'FinalizationRegistry'
2+
try {
3+
if (typeof FinalizationRegistry !== 'function') throw new Error('FinalizationRegistry missing');
4+
const fr = new FinalizationRegistry(function() {});
5+
if (!fr) throw new Error('FinalizationRegistry failed');
6+
console.log('OK');
7+
} catch (e) {
8+
console.log('NO');
9+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Inline assert helper
2+
function assert(cond, msg) { if (!cond) throw new Error("Assertion failed: " + msg); }
3+
4+
// Test Atomics.waitAsync existence
5+
(function testWaitAsyncExists() {
6+
assert(typeof Atomics.waitAsync === 'function', "Atomics.waitAsync should be a function");
7+
assert(Atomics.waitAsync.length === 4, "Atomics.waitAsync.length should be 4");
8+
console.log("PASS: Atomics.waitAsync exists with correct length");
9+
})();
10+
11+
// Test Atomics.waitAsync with non-equal value (synchronous not-equal result)
12+
(function testWaitAsyncNotEqual() {
13+
const sab = new SharedArrayBuffer(16);
14+
const ia = new Int32Array(sab);
15+
ia[0] = 42;
16+
17+
const result = Atomics.waitAsync(ia, 0, 0); // expected=0, actual=42
18+
assert(result.async === false, "Should be synchronous for not-equal, got async=" + result.async);
19+
assert(result.value === "not-equal", "Should be 'not-equal', got: " + result.value);
20+
console.log("PASS: Atomics.waitAsync returns not-equal synchronously");
21+
})();
22+
23+
// Test Atomics.waitAsync with equal value and immediate timeout
24+
(function testWaitAsyncImmediateTimeout() {
25+
const sab = new SharedArrayBuffer(16);
26+
const ia = new Int32Array(sab);
27+
ia[0] = 0;
28+
29+
const result = Atomics.waitAsync(ia, 0, 0, 0); // timeout=0
30+
assert(result.async === false, "Should be synchronous for timeout=0, got async=" + result.async);
31+
assert(result.value === "timed-out", "Should be 'timed-out', got: " + result.value);
32+
console.log("PASS: Atomics.waitAsync returns timed-out for zero timeout");
33+
})();
34+
35+
// Test Atomics.waitAsync returns promise with matching value
36+
(function testWaitAsyncPromise() {
37+
const sab = new SharedArrayBuffer(16);
38+
const ia = new Int32Array(sab);
39+
ia[0] = 0;
40+
41+
const result = Atomics.waitAsync(ia, 0, 0, 100); // timeout=100ms
42+
assert(result.async === true, "Should be async for matching value with timeout, got: " + result.async);
43+
assert(typeof result.value === 'object', "value should be a Promise object");
44+
console.log("PASS: Atomics.waitAsync returns {async: true, value: Promise}");
45+
})();
46+
47+
// Test Atomics.waitAsync validation
48+
(function testWaitAsyncValidation() {
49+
// Must be Int32Array or BigInt64Array
50+
try {
51+
const sab = new SharedArrayBuffer(16);
52+
const ua = new Uint8Array(sab);
53+
Atomics.waitAsync(ua, 0, 0);
54+
assert(false, "Should throw for Uint8Array");
55+
} catch (e) {
56+
assert(e instanceof TypeError, "Should be TypeError");
57+
console.log("PASS: Atomics.waitAsync rejects non-Int32Array");
58+
}
59+
60+
// Must be SharedArrayBuffer
61+
try {
62+
const ab = new ArrayBuffer(16);
63+
const ia = new Int32Array(ab);
64+
Atomics.waitAsync(ia, 0, 0);
65+
assert(false, "Should throw for non-shared buffer");
66+
} catch (e) {
67+
assert(e instanceof TypeError, "Should be TypeError");
68+
console.log("PASS: Atomics.waitAsync rejects non-SharedArrayBuffer");
69+
}
70+
})();
71+
72+
console.log("All Atomics.waitAsync tests passed!");
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Inline assert helper
2+
function assert(cond, msg) { if (!cond) throw new Error("Assertion failed: " + msg); }
3+
4+
// Test FinalizationRegistry basic functionality
5+
(function testFRBasic() {
6+
let called = false;
7+
const fr = new FinalizationRegistry(function(heldValue) {
8+
called = true;
9+
});
10+
11+
assert(fr instanceof FinalizationRegistry, "should be instanceof FinalizationRegistry");
12+
console.log("PASS: FinalizationRegistry basic creation");
13+
})();
14+
15+
// Test FinalizationRegistry.register
16+
(function testFRRegister() {
17+
const fr = new FinalizationRegistry(function(heldValue) {});
18+
19+
const target = {};
20+
const result = fr.register(target, "held value");
21+
assert(result === undefined, "register should return undefined");
22+
console.log("PASS: FinalizationRegistry.register returns undefined");
23+
})();
24+
25+
// Test FinalizationRegistry.register with unregister token
26+
(function testFRRegisterWithToken() {
27+
const fr = new FinalizationRegistry(function(heldValue) {});
28+
29+
const target = {};
30+
const token = {};
31+
fr.register(target, "held value", token);
32+
console.log("PASS: FinalizationRegistry.register with unregister token");
33+
})();
34+
35+
// Test FinalizationRegistry.register validation
36+
(function testFRRegisterValidation() {
37+
const fr = new FinalizationRegistry(function() {});
38+
39+
// target must be an object
40+
try {
41+
fr.register(42, "held");
42+
assert(false, "Should throw for primitive target");
43+
} catch (e) {
44+
assert(e instanceof TypeError, "Should be TypeError");
45+
console.log("PASS: FR rejects primitive target");
46+
}
47+
48+
// target and heldValue cannot be the same
49+
try {
50+
const obj = {};
51+
fr.register(obj, obj);
52+
assert(false, "Should throw when target===heldValue");
53+
} catch (e) {
54+
assert(e instanceof TypeError, "Should be TypeError");
55+
console.log("PASS: FR rejects target===heldValue");
56+
}
57+
58+
// unregisterToken must be object/symbol/undefined
59+
try {
60+
fr.register({}, "held", 42);
61+
assert(false, "Should throw for primitive unregisterToken");
62+
} catch (e) {
63+
assert(e instanceof TypeError, "Should be TypeError");
64+
console.log("PASS: FR rejects primitive unregisterToken");
65+
}
66+
})();
67+
68+
// Test FinalizationRegistry.unregister
69+
(function testFRUnregister() {
70+
const fr = new FinalizationRegistry(function() {});
71+
72+
const target1 = {};
73+
const target2 = {};
74+
const token = {};
75+
76+
fr.register(target1, "held1", token);
77+
fr.register(target2, "held2", token);
78+
79+
const removed = fr.unregister(token);
80+
assert(removed === true, "unregister should return true when entries were removed");
81+
console.log("PASS: FinalizationRegistry.unregister removes entries");
82+
})();
83+
84+
// Test FinalizationRegistry.unregister with no matching token
85+
(function testFRUnregisterNoMatch() {
86+
const fr = new FinalizationRegistry(function() {});
87+
88+
const target = {};
89+
const token1 = {};
90+
const token2 = {};
91+
92+
fr.register(target, "held", token1);
93+
94+
const removed = fr.unregister(token2);
95+
assert(removed === false, "unregister should return false when no entries matched");
96+
console.log("PASS: FinalizationRegistry.unregister returns false for no match");
97+
})();
98+
99+
// Test FinalizationRegistry constructor validation
100+
(function testFRCtorValidation() {
101+
try {
102+
new FinalizationRegistry(42);
103+
assert(false, "Should throw for non-callable cleanup");
104+
} catch (e) {
105+
assert(e instanceof TypeError, "Should be TypeError");
106+
console.log("PASS: FR constructor rejects non-callable");
107+
}
108+
})();
109+
110+
// Test FinalizationRegistry toString tag
111+
(function testFRToStringTag() {
112+
const fr = new FinalizationRegistry(function() {});
113+
const tag = Object.prototype.toString.call(fr);
114+
assert(tag === "[object FinalizationRegistry]", "toStringTag should be FinalizationRegistry, got: " + tag);
115+
console.log("PASS: FinalizationRegistry toStringTag");
116+
})();
117+
118+
// Test FinalizationRegistry constructor properties
119+
(function testFRCtorProps() {
120+
assert(FinalizationRegistry.length === 1, "FinalizationRegistry.length should be 1");
121+
assert(FinalizationRegistry.name === "FinalizationRegistry", "FinalizationRegistry.name should be 'FinalizationRegistry'");
122+
console.log("PASS: FinalizationRegistry constructor properties");
123+
})();
124+
125+
console.log("All FinalizationRegistry tests passed!");

js-scripts/test_weakref.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Inline assert helper
2+
function assert(cond, msg) { if (!cond) throw new Error("Assertion failed: " + msg); }
3+
4+
// Test WeakRef basic functionality
5+
(function testWeakRefBasic() {
6+
let target = { name: "test" };
7+
const wr = new WeakRef(target);
8+
9+
// deref() should return the target while it's alive
10+
const derefed = wr.deref();
11+
assert(derefed === target, "WeakRef.deref() should return target");
12+
assert(derefed.name === "test", "deref().name should be 'test'");
13+
console.log("PASS: WeakRef basic deref");
14+
})();
15+
16+
// Test WeakRef with symbol
17+
(function testWeakRefSymbol() {
18+
const sym = Symbol("test");
19+
const wr = new WeakRef(sym);
20+
const derefed = wr.deref();
21+
assert(derefed === sym, "WeakRef.deref() should return symbol");
22+
console.log("PASS: WeakRef symbol deref");
23+
})();
24+
25+
// Test WeakRef constructor validation
26+
(function testWeakRefValidation() {
27+
// Cannot create WeakRef with primitive
28+
try {
29+
new WeakRef(42);
30+
assert(false, "Should have thrown");
31+
} catch (e) {
32+
assert(e instanceof TypeError, "Should be TypeError for primitive target");
33+
console.log("PASS: WeakRef rejects primitive target");
34+
}
35+
36+
try {
37+
new WeakRef("string");
38+
assert(false, "Should have thrown");
39+
} catch (e) {
40+
assert(e instanceof TypeError, "Should be TypeError for string target");
41+
console.log("PASS: WeakRef rejects string target");
42+
}
43+
44+
// Cannot create WeakRef with registered symbol
45+
try {
46+
new WeakRef(Symbol.for("registered"));
47+
assert(false, "Should have thrown");
48+
} catch (e) {
49+
assert(e instanceof TypeError, "Should be TypeError for registered symbol");
50+
console.log("PASS: WeakRef rejects registered symbol");
51+
}
52+
})();
53+
54+
// Test WeakRef toString tag
55+
(function testWeakRefToStringTag() {
56+
const wr = new WeakRef({});
57+
const tag = Object.prototype.toString.call(wr);
58+
assert(tag === "[object WeakRef]", "toStringTag should be WeakRef, got: " + tag);
59+
console.log("PASS: WeakRef toStringTag");
60+
})();
61+
62+
// Test WeakRef instanceof
63+
(function testWeakRefInstanceof() {
64+
const wr = new WeakRef({});
65+
assert(wr instanceof WeakRef, "should be instanceof WeakRef");
66+
console.log("PASS: WeakRef instanceof");
67+
})();
68+
69+
// Test WeakRef constructor properties
70+
(function testWeakRefCtorProps() {
71+
assert(WeakRef.length === 1, "WeakRef.length should be 1");
72+
assert(WeakRef.name === "WeakRef", "WeakRef.name should be 'WeakRef'");
73+
console.log("PASS: WeakRef constructor properties");
74+
})();
75+
76+
console.log("All WeakRef tests passed!");

src/core/eval.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13384,6 +13384,46 @@ pub fn evaluate_call_dispatch<'gc>(
1338413384
} else {
1338513385
Err(raise_type_error!(format!("Unknown WeakSet function: {}", name)).into())
1338613386
}
13387+
} else if name.starts_with("WeakRef.") {
13388+
if let Some(method) = name.strip_prefix("WeakRef.prototype.") {
13389+
let this_v = this_val.unwrap_or(&Value::Undefined);
13390+
if let Value::Object(obj) = this_v {
13391+
if slot_get_chained(obj, &InternalSlot::WeakRefMarker).is_some() {
13392+
Ok(crate::js_weakref::handle_weakref_instance_method(mc, obj, method, eval_args, env)?)
13393+
} else {
13394+
Err(raise_type_error!(format!("Method WeakRef.prototype.{} called on incompatible receiver", method)).into())
13395+
}
13396+
} else {
13397+
Err(raise_type_error!(format!("Method WeakRef.prototype.{} called on incompatible receiver", method)).into())
13398+
}
13399+
} else {
13400+
Err(raise_type_error!(format!("Unknown WeakRef function: {}", name)).into())
13401+
}
13402+
} else if name.starts_with("FinalizationRegistry.") {
13403+
if let Some(method) = name.strip_prefix("FinalizationRegistry.prototype.") {
13404+
let this_v = this_val.unwrap_or(&Value::Undefined);
13405+
if let Value::Object(obj) = this_v {
13406+
if slot_get_chained(obj, &InternalSlot::FRMarker).is_some() {
13407+
Ok(crate::js_finalization_registry::handle_fr_instance_method(
13408+
mc, obj, method, eval_args, env,
13409+
)?)
13410+
} else {
13411+
Err(raise_type_error!(format!(
13412+
"Method FinalizationRegistry.prototype.{} called on incompatible receiver",
13413+
method
13414+
))
13415+
.into())
13416+
}
13417+
} else {
13418+
Err(raise_type_error!(format!(
13419+
"Method FinalizationRegistry.prototype.{} called on incompatible receiver",
13420+
method
13421+
))
13422+
.into())
13423+
}
13424+
} else {
13425+
Err(raise_type_error!(format!("Unknown FinalizationRegistry function: {}", name)).into())
13426+
}
1338713427
} else if name == "Set[Symbol.species]" {
1338813428
let this_v = this_val.unwrap_or(&Value::Undefined);
1338913429
Ok(this_v.clone())
@@ -17134,6 +17174,7 @@ fn evaluate_expr_property<'gc>(
1713417174
| "WeakSet.prototype.add"
1713517175
| "WeakSet.prototype.has"
1713617176
| "WeakSet.prototype.delete"
17177+
| "FinalizationRegistry.prototype.unregister"
1713717178
| "Number.isNaN"
1713817179
| "Number.isFinite"
1713917180
| "Number.isInteger"
@@ -17237,6 +17278,7 @@ fn evaluate_expr_property<'gc>(
1723717278
| "Map.prototype.getOrInsert"
1723817279
| "Map.prototype.getOrInsertComputed"
1723917280
| "WeakMap.prototype.set"
17281+
| "FinalizationRegistry.prototype.register"
1724017282
| "WeakMap.prototype.getOrInsert"
1724117283
| "WeakMap.prototype.getOrInsertComputed"
1724217284
| "Array.prototype.toSpliced"
@@ -18707,6 +18749,7 @@ fn evaluate_expr_index<'gc>(
1870718749
| "WeakSet.prototype.add"
1870818750
| "WeakSet.prototype.has"
1870918751
| "WeakSet.prototype.delete"
18752+
| "FinalizationRegistry.prototype.unregister"
1871018753
| "Number.isNaN"
1871118754
| "Number.isFinite"
1871218755
| "Number.isInteger"
@@ -18810,6 +18853,7 @@ fn evaluate_expr_index<'gc>(
1881018853
| "WeakMap.prototype.set"
1881118854
| "WeakMap.prototype.getOrInsert"
1881218855
| "WeakMap.prototype.getOrInsertComputed"
18856+
| "FinalizationRegistry.prototype.register"
1881318857
| "Array.prototype.toSpliced"
1881418858
| "Array.prototype.with"
1881518859
| "Math.atan2"
@@ -23339,8 +23383,12 @@ fn evaluate_expr_new<'gc>(
2333923383
return crate::js_proxy::handle_proxy_constructor(mc, &eval_args, env);
2334023384
} else if name_str == "WeakMap" {
2334123385
return crate::js_weakmap::handle_weakmap_constructor(mc, &eval_args, env, None);
23386+
} else if name_str == "WeakRef" {
23387+
return crate::js_weakref::handle_weakref_constructor(mc, &eval_args, env, None);
2334223388
} else if name_str == "WeakSet" {
2334323389
return crate::js_weakset::handle_weakset_constructor(mc, &eval_args, env, None);
23390+
} else if name_str == "FinalizationRegistry" {
23391+
return crate::js_finalization_registry::handle_fr_constructor(mc, &eval_args, env, None);
2334423392
} else if name_str == "Set" {
2334523393
return crate::js_set::handle_set_constructor(mc, &eval_args, env, None);
2334623394
} else if name_str == "ArrayBuffer" {

0 commit comments

Comments
 (0)