we (web engine): Experimental web browser project to understand the limits of Claude

Implement JS built-in Map, Set, WeakMap, WeakSet collections

Add Map, Set, WeakMap, and WeakSet constructors with prototype
methods to the JavaScript engine:

- Map: constructor (from iterable), set/get/has/delete/clear,
size property, forEach, keys/values/entries iterators.
SameValueZero key equality, insertion order preserved.

- Set: constructor (from iterable), add/has/delete/clear,
size property, forEach, keys/values/entries iterators.
SameValueZero value equality, insertion order preserved.

- WeakMap: constructor, set/get/has/delete. Keys must be objects.

- WeakSet: constructor, add/has/delete. Values must be objects.

Internal storage uses indexed entry objects on the GC heap with
deleted-slot markers for stable insertion-order iteration.

30 new integration tests covering all methods and edge cases
(NaN keys, object keys, chaining, uniqueness, constructor from
arrays, insertion order preservation).

Also removes Map/Set from test262 unsupported features list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+1140 -2
+845
crates/js/src/builtins.rs
··· 243 243 // Create and register RegExp constructor. 244 244 init_regexp_builtins(vm); 245 245 246 + // Create and register Map, Set, WeakMap, WeakSet constructors. 247 + init_map_set_builtins(vm); 248 + 246 249 // Create and register JSON object (static methods only). 247 250 init_json_object(vm); 248 251 ··· 3812 3815 let pattern = regexp_get_pattern(ctx.gc, &ctx.this).unwrap_or_default(); 3813 3816 let flags = regexp_get_flags(ctx.gc, &ctx.this).unwrap_or_default(); 3814 3817 Ok(Value::String(format!("/{}/{}", pattern, flags))) 3818 + } 3819 + 3820 + // ── Map built-in ───────────────────────────────────────────── 3821 + 3822 + thread_local! { 3823 + static MAP_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) }; 3824 + static SET_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) }; 3825 + static WEAKMAP_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) }; 3826 + static WEAKSET_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) }; 3827 + } 3828 + 3829 + fn init_map_set_builtins(vm: &mut Vm) { 3830 + // ── Map ── 3831 + let mut map_proto_data = ObjectData::new(); 3832 + if let Some(proto) = vm.object_prototype { 3833 + map_proto_data.prototype = Some(proto); 3834 + } 3835 + let map_proto = vm.gc.alloc(HeapObject::Object(map_proto_data)); 3836 + init_map_prototype(&mut vm.gc, map_proto); 3837 + MAP_PROTO.with(|cell| cell.set(Some(map_proto))); 3838 + 3839 + let map_ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { 3840 + name: "Map".to_string(), 3841 + kind: FunctionKind::Native(NativeFunc { 3842 + callback: map_constructor, 3843 + }), 3844 + prototype_obj: Some(map_proto), 3845 + properties: HashMap::new(), 3846 + upvalues: Vec::new(), 3847 + }))); 3848 + vm.set_global("Map", Value::Function(map_ctor)); 3849 + 3850 + // ── Set ── 3851 + let mut set_proto_data = ObjectData::new(); 3852 + if let Some(proto) = vm.object_prototype { 3853 + set_proto_data.prototype = Some(proto); 3854 + } 3855 + let set_proto = vm.gc.alloc(HeapObject::Object(set_proto_data)); 3856 + init_set_prototype(&mut vm.gc, set_proto); 3857 + SET_PROTO.with(|cell| cell.set(Some(set_proto))); 3858 + 3859 + let set_ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { 3860 + name: "Set".to_string(), 3861 + kind: FunctionKind::Native(NativeFunc { 3862 + callback: set_constructor, 3863 + }), 3864 + prototype_obj: Some(set_proto), 3865 + properties: HashMap::new(), 3866 + upvalues: Vec::new(), 3867 + }))); 3868 + vm.set_global("Set", Value::Function(set_ctor)); 3869 + 3870 + // ── WeakMap ── 3871 + let mut wm_proto_data = ObjectData::new(); 3872 + if let Some(proto) = vm.object_prototype { 3873 + wm_proto_data.prototype = Some(proto); 3874 + } 3875 + let wm_proto = vm.gc.alloc(HeapObject::Object(wm_proto_data)); 3876 + init_weakmap_prototype(&mut vm.gc, wm_proto); 3877 + WEAKMAP_PROTO.with(|cell| cell.set(Some(wm_proto))); 3878 + 3879 + let wm_ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { 3880 + name: "WeakMap".to_string(), 3881 + kind: FunctionKind::Native(NativeFunc { 3882 + callback: weakmap_constructor, 3883 + }), 3884 + prototype_obj: Some(wm_proto), 3885 + properties: HashMap::new(), 3886 + upvalues: Vec::new(), 3887 + }))); 3888 + vm.set_global("WeakMap", Value::Function(wm_ctor)); 3889 + 3890 + // ── WeakSet ── 3891 + let mut ws_proto_data = ObjectData::new(); 3892 + if let Some(proto) = vm.object_prototype { 3893 + ws_proto_data.prototype = Some(proto); 3894 + } 3895 + let ws_proto = vm.gc.alloc(HeapObject::Object(ws_proto_data)); 3896 + init_weakset_prototype(&mut vm.gc, ws_proto); 3897 + WEAKSET_PROTO.with(|cell| cell.set(Some(ws_proto))); 3898 + 3899 + let ws_ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { 3900 + name: "WeakSet".to_string(), 3901 + kind: FunctionKind::Native(NativeFunc { 3902 + callback: weakset_constructor, 3903 + }), 3904 + prototype_obj: Some(ws_proto), 3905 + properties: HashMap::new(), 3906 + upvalues: Vec::new(), 3907 + }))); 3908 + vm.set_global("WeakSet", Value::Function(ws_ctor)); 3909 + } 3910 + 3911 + // ── Map / Set internal helpers ─────────────────────────────── 3912 + 3913 + /// Internal storage key for Map/Set entries. 3914 + /// We store entries as a hidden object with indexed key/value pairs: 3915 + /// __entries__: GcRef to an object with "0_k", "0_v", "1_k", "1_v", ... 3916 + /// __entry_count__: total slots allocated (some may be deleted) 3917 + /// __live_count__: number of non-deleted entries 3918 + /// Deleted entries have their key set to a special "__deleted__" marker. 3919 + const ENTRIES_KEY: &str = "__entries__"; 3920 + const ENTRY_COUNT_KEY: &str = "__entry_count__"; 3921 + const LIVE_COUNT_KEY: &str = "__live_count__"; 3922 + const DELETED_MARKER: &str = "__deleted__"; 3923 + 3924 + /// Create a new empty Map/Set internal storage object. 3925 + fn make_collection_obj(gc: &mut Gc<HeapObject>, proto: Option<GcRef>) -> GcRef { 3926 + let entries_obj = gc.alloc(HeapObject::Object(ObjectData::new())); 3927 + let mut data = ObjectData::new(); 3928 + if let Some(p) = proto { 3929 + data.prototype = Some(p); 3930 + } 3931 + data.properties.insert( 3932 + ENTRIES_KEY.to_string(), 3933 + Property::builtin(Value::Object(entries_obj)), 3934 + ); 3935 + data.properties.insert( 3936 + ENTRY_COUNT_KEY.to_string(), 3937 + Property::builtin(Value::Number(0.0)), 3938 + ); 3939 + data.properties.insert( 3940 + LIVE_COUNT_KEY.to_string(), 3941 + Property::builtin(Value::Number(0.0)), 3942 + ); 3943 + // size is a read-only, non-enumerable property. 3944 + data.properties 3945 + .insert("size".to_string(), Property::builtin(Value::Number(0.0))); 3946 + gc.alloc(HeapObject::Object(data)) 3947 + } 3948 + 3949 + /// Get the entries object GcRef from a Map/Set object. 3950 + fn collection_entries(gc: &Gc<HeapObject>, obj: &Value) -> Option<GcRef> { 3951 + let gc_ref = obj.gc_ref()?; 3952 + let heap = gc.get(gc_ref)?; 3953 + if let HeapObject::Object(data) = heap { 3954 + if let Some(prop) = data.properties.get(ENTRIES_KEY) { 3955 + return prop.value.gc_ref(); 3956 + } 3957 + } 3958 + None 3959 + } 3960 + 3961 + /// Get the entry count from a Map/Set object. 3962 + fn collection_entry_count(gc: &Gc<HeapObject>, obj: &Value) -> usize { 3963 + let gc_ref = match obj.gc_ref() { 3964 + Some(r) => r, 3965 + None => return 0, 3966 + }; 3967 + match gc.get(gc_ref) { 3968 + Some(HeapObject::Object(data)) => data 3969 + .properties 3970 + .get(ENTRY_COUNT_KEY) 3971 + .map(|p| p.value.to_number() as usize) 3972 + .unwrap_or(0), 3973 + _ => 0, 3974 + } 3975 + } 3976 + 3977 + /// Get the live count from a Map/Set object. 3978 + fn collection_live_count(gc: &Gc<HeapObject>, obj: &Value) -> usize { 3979 + let gc_ref = match obj.gc_ref() { 3980 + Some(r) => r, 3981 + None => return 0, 3982 + }; 3983 + match gc.get(gc_ref) { 3984 + Some(HeapObject::Object(data)) => data 3985 + .properties 3986 + .get(LIVE_COUNT_KEY) 3987 + .map(|p| p.value.to_number() as usize) 3988 + .unwrap_or(0), 3989 + _ => 0, 3990 + } 3991 + } 3992 + 3993 + /// Set the entry count on a Map/Set object and update the `size` property. 3994 + fn set_collection_count(gc: &mut Gc<HeapObject>, obj: &Value, entry_count: usize, live: usize) { 3995 + let gc_ref = match obj.gc_ref() { 3996 + Some(r) => r, 3997 + None => return, 3998 + }; 3999 + if let Some(HeapObject::Object(data)) = gc.get_mut(gc_ref) { 4000 + data.properties.insert( 4001 + ENTRY_COUNT_KEY.to_string(), 4002 + Property::builtin(Value::Number(entry_count as f64)), 4003 + ); 4004 + data.properties.insert( 4005 + LIVE_COUNT_KEY.to_string(), 4006 + Property::builtin(Value::Number(live as f64)), 4007 + ); 4008 + data.properties.insert( 4009 + "size".to_string(), 4010 + Property::builtin(Value::Number(live as f64)), 4011 + ); 4012 + } 4013 + } 4014 + 4015 + /// Get the key at index `i` in the entries object (returns None if deleted or missing). 4016 + fn entry_key_at(gc: &Gc<HeapObject>, entries: GcRef, i: usize) -> Option<Value> { 4017 + match gc.get(entries) { 4018 + Some(HeapObject::Object(data)) => { 4019 + let k = format!("{i}_k"); 4020 + let prop = data.properties.get(&k)?; 4021 + if let Value::String(s) = &prop.value { 4022 + if s == DELETED_MARKER { 4023 + return None; 4024 + } 4025 + } 4026 + Some(prop.value.clone()) 4027 + } 4028 + _ => None, 4029 + } 4030 + } 4031 + 4032 + /// Get the value at index `i` in the entries object. 4033 + fn entry_value_at(gc: &Gc<HeapObject>, entries: GcRef, i: usize) -> Value { 4034 + match gc.get(entries) { 4035 + Some(HeapObject::Object(data)) => { 4036 + let v = format!("{i}_v"); 4037 + data.properties 4038 + .get(&v) 4039 + .map(|p| p.value.clone()) 4040 + .unwrap_or(Value::Undefined) 4041 + } 4042 + _ => Value::Undefined, 4043 + } 4044 + } 4045 + 4046 + /// Set an entry at index `i`. 4047 + fn set_entry_at(gc: &mut Gc<HeapObject>, entries: GcRef, i: usize, key: Value, value: Value) { 4048 + if let Some(HeapObject::Object(data)) = gc.get_mut(entries) { 4049 + data.properties 4050 + .insert(format!("{i}_k"), Property::builtin(key)); 4051 + data.properties 4052 + .insert(format!("{i}_v"), Property::builtin(value)); 4053 + } 4054 + } 4055 + 4056 + /// Mark entry at index `i` as deleted. 4057 + fn delete_entry_at(gc: &mut Gc<HeapObject>, entries: GcRef, i: usize) { 4058 + if let Some(HeapObject::Object(data)) = gc.get_mut(entries) { 4059 + data.properties.insert( 4060 + format!("{i}_k"), 4061 + Property::builtin(Value::String(DELETED_MARKER.to_string())), 4062 + ); 4063 + data.properties.remove(&format!("{i}_v")); 4064 + } 4065 + } 4066 + 4067 + /// Find the index of a key in the entries, using SameValueZero. 4068 + fn find_key_index(gc: &Gc<HeapObject>, entries: GcRef, count: usize, key: &Value) -> Option<usize> { 4069 + for i in 0..count { 4070 + if let Some(existing) = entry_key_at(gc, entries, i) { 4071 + if same_value_zero(&existing, key) { 4072 + return Some(i); 4073 + } 4074 + } 4075 + } 4076 + None 4077 + } 4078 + 4079 + // ── Map constructor & prototype ────────────────────────────── 4080 + 4081 + fn map_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4082 + let proto = MAP_PROTO.with(|cell| cell.get()); 4083 + let obj_ref = make_collection_obj(ctx.gc, proto); 4084 + let obj = Value::Object(obj_ref); 4085 + 4086 + // If an iterable argument is provided, add entries from it. 4087 + // We support arrays of [key, value] pairs. 4088 + if let Some(arg) = args.first() { 4089 + if !matches!(arg, Value::Undefined | Value::Null) { 4090 + if let Some(arr_ref) = arg.gc_ref() { 4091 + let len = array_length(ctx.gc, arr_ref); 4092 + for i in 0..len { 4093 + let pair = get_property(ctx.gc, &Value::Object(arr_ref), &i.to_string()); 4094 + if let Some(pair_ref) = pair.gc_ref() { 4095 + let k = get_property(ctx.gc, &Value::Object(pair_ref), "0"); 4096 + let v = get_property(ctx.gc, &Value::Object(pair_ref), "1"); 4097 + map_set_internal(ctx.gc, &obj, k, v); 4098 + } 4099 + } 4100 + } 4101 + } 4102 + } 4103 + 4104 + Ok(obj) 4105 + } 4106 + 4107 + fn map_set_internal(gc: &mut Gc<HeapObject>, map: &Value, key: Value, value: Value) { 4108 + let entries = match collection_entries(gc, map) { 4109 + Some(e) => e, 4110 + None => return, 4111 + }; 4112 + let count = collection_entry_count(gc, map); 4113 + let live = collection_live_count(gc, map); 4114 + 4115 + // Check if key already exists. 4116 + if let Some(idx) = find_key_index(gc, entries, count, &key) { 4117 + set_entry_at(gc, entries, idx, key, value); 4118 + return; 4119 + } 4120 + 4121 + // Add new entry. 4122 + set_entry_at(gc, entries, count, key, value); 4123 + set_collection_count(gc, map, count + 1, live + 1); 4124 + } 4125 + 4126 + fn init_map_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 4127 + let methods: &[NativeMethod] = &[ 4128 + ("set", map_proto_set), 4129 + ("get", map_proto_get), 4130 + ("has", map_proto_has), 4131 + ("delete", map_proto_delete), 4132 + ("clear", map_proto_clear), 4133 + ("forEach", map_proto_for_each), 4134 + ("keys", map_proto_keys), 4135 + ("values", map_proto_values), 4136 + ("entries", map_proto_entries), 4137 + ]; 4138 + for &(name, callback) in methods { 4139 + let f = make_native(gc, name, callback); 4140 + set_builtin_prop(gc, proto, name, Value::Function(f)); 4141 + } 4142 + } 4143 + 4144 + fn map_proto_set(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4145 + let key = args.first().cloned().unwrap_or(Value::Undefined); 4146 + let value = args.get(1).cloned().unwrap_or(Value::Undefined); 4147 + // Normalize -0 to +0 for key. 4148 + let key = normalize_zero(key); 4149 + map_set_internal(ctx.gc, &ctx.this, key, value); 4150 + Ok(ctx.this.clone()) 4151 + } 4152 + 4153 + fn map_proto_get(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4154 + let key = args.first().cloned().unwrap_or(Value::Undefined); 4155 + let key = normalize_zero(key); 4156 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4157 + Some(e) => e, 4158 + None => return Ok(Value::Undefined), 4159 + }; 4160 + let count = collection_entry_count(ctx.gc, &ctx.this); 4161 + if let Some(idx) = find_key_index(ctx.gc, entries, count, &key) { 4162 + return Ok(entry_value_at(ctx.gc, entries, idx)); 4163 + } 4164 + Ok(Value::Undefined) 4165 + } 4166 + 4167 + fn map_proto_has(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4168 + let key = args.first().cloned().unwrap_or(Value::Undefined); 4169 + let key = normalize_zero(key); 4170 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4171 + Some(e) => e, 4172 + None => return Ok(Value::Boolean(false)), 4173 + }; 4174 + let count = collection_entry_count(ctx.gc, &ctx.this); 4175 + Ok(Value::Boolean( 4176 + find_key_index(ctx.gc, entries, count, &key).is_some(), 4177 + )) 4178 + } 4179 + 4180 + fn map_proto_delete(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4181 + let key = args.first().cloned().unwrap_or(Value::Undefined); 4182 + let key = normalize_zero(key); 4183 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4184 + Some(e) => e, 4185 + None => return Ok(Value::Boolean(false)), 4186 + }; 4187 + let count = collection_entry_count(ctx.gc, &ctx.this); 4188 + let live = collection_live_count(ctx.gc, &ctx.this); 4189 + if let Some(idx) = find_key_index(ctx.gc, entries, count, &key) { 4190 + delete_entry_at(ctx.gc, entries, idx); 4191 + set_collection_count(ctx.gc, &ctx.this, count, live.saturating_sub(1)); 4192 + return Ok(Value::Boolean(true)); 4193 + } 4194 + Ok(Value::Boolean(false)) 4195 + } 4196 + 4197 + fn map_proto_clear(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4198 + clear_collection(ctx.gc, &ctx.this); 4199 + Ok(Value::Undefined) 4200 + } 4201 + 4202 + /// Clear all entries from a Map/Set collection. 4203 + fn clear_collection(gc: &mut Gc<HeapObject>, obj: &Value) { 4204 + let new_entries = gc.alloc(HeapObject::Object(ObjectData::new())); 4205 + if let Some(gc_ref) = obj.gc_ref() { 4206 + if let Some(HeapObject::Object(data)) = gc.get_mut(gc_ref) { 4207 + data.properties.insert( 4208 + ENTRIES_KEY.to_string(), 4209 + Property::builtin(Value::Object(new_entries)), 4210 + ); 4211 + data.properties.insert( 4212 + ENTRY_COUNT_KEY.to_string(), 4213 + Property::builtin(Value::Number(0.0)), 4214 + ); 4215 + data.properties.insert( 4216 + LIVE_COUNT_KEY.to_string(), 4217 + Property::builtin(Value::Number(0.0)), 4218 + ); 4219 + data.properties 4220 + .insert("size".to_string(), Property::builtin(Value::Number(0.0))); 4221 + } 4222 + } 4223 + } 4224 + 4225 + fn map_proto_for_each(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4226 + let callback = match args.first() { 4227 + Some(Value::Function(r)) => *r, 4228 + _ => { 4229 + return Err(RuntimeError::type_error( 4230 + "Map.prototype.forEach requires a function argument", 4231 + )) 4232 + } 4233 + }; 4234 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4235 + Some(e) => e, 4236 + None => return Ok(Value::Undefined), 4237 + }; 4238 + let count = collection_entry_count(ctx.gc, &ctx.this); 4239 + // Collect entries first to avoid borrow issues. 4240 + let mut pairs = Vec::new(); 4241 + for i in 0..count { 4242 + if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4243 + let v = entry_value_at(ctx.gc, entries, i); 4244 + pairs.push((k, v)); 4245 + } 4246 + } 4247 + let this_val = ctx.this.clone(); 4248 + for (k, v) in pairs { 4249 + call_native_callback(ctx.gc, callback, &[v, k, this_val.clone()])?; 4250 + } 4251 + Ok(Value::Undefined) 4252 + } 4253 + 4254 + fn map_proto_keys(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4255 + map_proto_iter(args, ctx, IterKind::Keys) 4256 + } 4257 + 4258 + fn map_proto_values(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4259 + map_proto_iter(args, ctx, IterKind::Values) 4260 + } 4261 + 4262 + fn map_proto_entries(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4263 + map_proto_iter(args, ctx, IterKind::Entries) 4264 + } 4265 + 4266 + enum IterKind { 4267 + Keys, 4268 + Values, 4269 + Entries, 4270 + } 4271 + 4272 + fn map_proto_iter( 4273 + _args: &[Value], 4274 + ctx: &mut NativeContext, 4275 + kind: IterKind, 4276 + ) -> Result<Value, RuntimeError> { 4277 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4278 + Some(e) => e, 4279 + None => return Ok(make_value_array(ctx.gc, &[])), 4280 + }; 4281 + let count = collection_entry_count(ctx.gc, &ctx.this); 4282 + let mut items = Vec::new(); 4283 + for i in 0..count { 4284 + if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4285 + match kind { 4286 + IterKind::Keys => items.push(k), 4287 + IterKind::Values => { 4288 + let v = entry_value_at(ctx.gc, entries, i); 4289 + items.push(v); 4290 + } 4291 + IterKind::Entries => { 4292 + let v = entry_value_at(ctx.gc, entries, i); 4293 + let pair = make_value_array(ctx.gc, &[k, v]); 4294 + items.push(pair); 4295 + } 4296 + } 4297 + } 4298 + } 4299 + Ok(make_value_array(ctx.gc, &items)) 4300 + } 4301 + 4302 + /// Normalize -0 to +0 for Map/Set key equality. 4303 + fn normalize_zero(val: Value) -> Value { 4304 + if let Value::Number(n) = &val { 4305 + if *n == 0.0 { 4306 + return Value::Number(0.0); 4307 + } 4308 + } 4309 + val 4310 + } 4311 + 4312 + /// Helper to get a property from an object value by key (used for reading iterable pairs). 4313 + fn get_property(gc: &Gc<HeapObject>, obj: &Value, key: &str) -> Value { 4314 + let gc_ref = match obj.gc_ref() { 4315 + Some(r) => r, 4316 + None => return Value::Undefined, 4317 + }; 4318 + match gc.get(gc_ref) { 4319 + Some(HeapObject::Object(data)) => data 4320 + .properties 4321 + .get(key) 4322 + .map(|p| p.value.clone()) 4323 + .unwrap_or(Value::Undefined), 4324 + _ => Value::Undefined, 4325 + } 4326 + } 4327 + 4328 + /// Call a native callback function (for forEach). 4329 + fn call_native_callback( 4330 + gc: &mut Gc<HeapObject>, 4331 + func_ref: GcRef, 4332 + args: &[Value], 4333 + ) -> Result<Value, RuntimeError> { 4334 + match gc.get(func_ref) { 4335 + Some(HeapObject::Function(fdata)) => match &fdata.kind { 4336 + FunctionKind::Native(native) => { 4337 + let cb = native.callback; 4338 + let mut ctx = NativeContext { 4339 + gc, 4340 + this: Value::Undefined, 4341 + }; 4342 + cb(args, &mut ctx) 4343 + } 4344 + FunctionKind::Bytecode(_) => Err(RuntimeError::type_error( 4345 + "bytecode callbacks in forEach not yet supported", 4346 + )), 4347 + }, 4348 + _ => Err(RuntimeError::type_error("not a function")), 4349 + } 4350 + } 4351 + 4352 + // ── Set constructor & prototype ────────────────────────────── 4353 + 4354 + fn set_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4355 + let proto = SET_PROTO.with(|cell| cell.get()); 4356 + let obj_ref = make_collection_obj(ctx.gc, proto); 4357 + let obj = Value::Object(obj_ref); 4358 + 4359 + // If an iterable argument is provided, add values from it. 4360 + if let Some(arg) = args.first() { 4361 + if !matches!(arg, Value::Undefined | Value::Null) { 4362 + if let Some(arr_ref) = arg.gc_ref() { 4363 + let len = array_length(ctx.gc, arr_ref); 4364 + for i in 0..len { 4365 + let v = get_property(ctx.gc, &Value::Object(arr_ref), &i.to_string()); 4366 + set_add_internal(ctx.gc, &obj, v); 4367 + } 4368 + } 4369 + } 4370 + } 4371 + 4372 + Ok(obj) 4373 + } 4374 + 4375 + fn set_add_internal(gc: &mut Gc<HeapObject>, set: &Value, value: Value) { 4376 + let entries = match collection_entries(gc, set) { 4377 + Some(e) => e, 4378 + None => return, 4379 + }; 4380 + let count = collection_entry_count(gc, set); 4381 + let live = collection_live_count(gc, set); 4382 + 4383 + // Check if value already exists. 4384 + if find_key_index(gc, entries, count, &value).is_some() { 4385 + return; 4386 + } 4387 + 4388 + // Add new entry (value stored as key, value slot unused). 4389 + set_entry_at(gc, entries, count, value, Value::Undefined); 4390 + set_collection_count(gc, set, count + 1, live + 1); 4391 + } 4392 + 4393 + fn init_set_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 4394 + let methods: &[NativeMethod] = &[ 4395 + ("add", set_proto_add), 4396 + ("has", set_proto_has), 4397 + ("delete", set_proto_delete), 4398 + ("clear", set_proto_clear), 4399 + ("forEach", set_proto_for_each), 4400 + ("keys", set_proto_keys), 4401 + ("values", set_proto_values), 4402 + ("entries", set_proto_entries), 4403 + ]; 4404 + for &(name, callback) in methods { 4405 + let f = make_native(gc, name, callback); 4406 + set_builtin_prop(gc, proto, name, Value::Function(f)); 4407 + } 4408 + } 4409 + 4410 + fn set_proto_add(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4411 + let value = args.first().cloned().unwrap_or(Value::Undefined); 4412 + let value = normalize_zero(value); 4413 + set_add_internal(ctx.gc, &ctx.this, value); 4414 + Ok(ctx.this.clone()) 4415 + } 4416 + 4417 + fn set_proto_has(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4418 + let value = args.first().cloned().unwrap_or(Value::Undefined); 4419 + let value = normalize_zero(value); 4420 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4421 + Some(e) => e, 4422 + None => return Ok(Value::Boolean(false)), 4423 + }; 4424 + let count = collection_entry_count(ctx.gc, &ctx.this); 4425 + Ok(Value::Boolean( 4426 + find_key_index(ctx.gc, entries, count, &value).is_some(), 4427 + )) 4428 + } 4429 + 4430 + fn set_proto_delete(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4431 + let value = args.first().cloned().unwrap_or(Value::Undefined); 4432 + let value = normalize_zero(value); 4433 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4434 + Some(e) => e, 4435 + None => return Ok(Value::Boolean(false)), 4436 + }; 4437 + let count = collection_entry_count(ctx.gc, &ctx.this); 4438 + let live = collection_live_count(ctx.gc, &ctx.this); 4439 + if let Some(idx) = find_key_index(ctx.gc, entries, count, &value) { 4440 + delete_entry_at(ctx.gc, entries, idx); 4441 + set_collection_count(ctx.gc, &ctx.this, count, live.saturating_sub(1)); 4442 + return Ok(Value::Boolean(true)); 4443 + } 4444 + Ok(Value::Boolean(false)) 4445 + } 4446 + 4447 + fn set_proto_clear(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4448 + clear_collection(ctx.gc, &ctx.this); 4449 + Ok(Value::Undefined) 4450 + } 4451 + 4452 + fn set_proto_for_each(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4453 + let callback = match args.first() { 4454 + Some(Value::Function(r)) => *r, 4455 + _ => { 4456 + return Err(RuntimeError::type_error( 4457 + "Set.prototype.forEach requires a function argument", 4458 + )) 4459 + } 4460 + }; 4461 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4462 + Some(e) => e, 4463 + None => return Ok(Value::Undefined), 4464 + }; 4465 + let count = collection_entry_count(ctx.gc, &ctx.this); 4466 + let mut values = Vec::new(); 4467 + for i in 0..count { 4468 + if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4469 + values.push(k); 4470 + } 4471 + } 4472 + let this_val = ctx.this.clone(); 4473 + for v in values { 4474 + call_native_callback(ctx.gc, callback, &[v.clone(), v, this_val.clone()])?; 4475 + } 4476 + Ok(Value::Undefined) 4477 + } 4478 + 4479 + fn set_proto_keys(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4480 + set_proto_values(_args, ctx) 4481 + } 4482 + 4483 + fn set_proto_values(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4484 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4485 + Some(e) => e, 4486 + None => return Ok(make_value_array(ctx.gc, &[])), 4487 + }; 4488 + let count = collection_entry_count(ctx.gc, &ctx.this); 4489 + let mut items = Vec::new(); 4490 + for i in 0..count { 4491 + if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4492 + items.push(k); 4493 + } 4494 + } 4495 + Ok(make_value_array(ctx.gc, &items)) 4496 + } 4497 + 4498 + fn set_proto_entries(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4499 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4500 + Some(e) => e, 4501 + None => return Ok(make_value_array(ctx.gc, &[])), 4502 + }; 4503 + let count = collection_entry_count(ctx.gc, &ctx.this); 4504 + let mut items = Vec::new(); 4505 + for i in 0..count { 4506 + if let Some(k) = entry_key_at(ctx.gc, entries, i) { 4507 + let pair = make_value_array(ctx.gc, &[k.clone(), k]); 4508 + items.push(pair); 4509 + } 4510 + } 4511 + Ok(make_value_array(ctx.gc, &items)) 4512 + } 4513 + 4514 + // ── WeakMap constructor & prototype ────────────────────────── 4515 + 4516 + fn weakmap_constructor(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4517 + let proto = WEAKMAP_PROTO.with(|cell| cell.get()); 4518 + let obj_ref = make_collection_obj(ctx.gc, proto); 4519 + Ok(Value::Object(obj_ref)) 4520 + } 4521 + 4522 + fn is_object_value(val: &Value) -> bool { 4523 + matches!(val, Value::Object(_) | Value::Function(_)) 4524 + } 4525 + 4526 + fn init_weakmap_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 4527 + let methods: &[NativeMethod] = &[ 4528 + ("set", weakmap_proto_set), 4529 + ("get", weakmap_proto_get), 4530 + ("has", weakmap_proto_has), 4531 + ("delete", weakmap_proto_delete), 4532 + ]; 4533 + for &(name, callback) in methods { 4534 + let f = make_native(gc, name, callback); 4535 + set_builtin_prop(gc, proto, name, Value::Function(f)); 4536 + } 4537 + } 4538 + 4539 + fn weakmap_proto_set(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4540 + let key = args.first().cloned().unwrap_or(Value::Undefined); 4541 + if !is_object_value(&key) { 4542 + return Err(RuntimeError::type_error("WeakMap key must be an object")); 4543 + } 4544 + let value = args.get(1).cloned().unwrap_or(Value::Undefined); 4545 + map_set_internal(ctx.gc, &ctx.this, key, value); 4546 + Ok(ctx.this.clone()) 4547 + } 4548 + 4549 + fn weakmap_proto_get(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4550 + let key = args.first().cloned().unwrap_or(Value::Undefined); 4551 + if !is_object_value(&key) { 4552 + return Ok(Value::Undefined); 4553 + } 4554 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4555 + Some(e) => e, 4556 + None => return Ok(Value::Undefined), 4557 + }; 4558 + let count = collection_entry_count(ctx.gc, &ctx.this); 4559 + if let Some(idx) = find_key_index(ctx.gc, entries, count, &key) { 4560 + return Ok(entry_value_at(ctx.gc, entries, idx)); 4561 + } 4562 + Ok(Value::Undefined) 4563 + } 4564 + 4565 + fn weakmap_proto_has(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4566 + let key = args.first().cloned().unwrap_or(Value::Undefined); 4567 + if !is_object_value(&key) { 4568 + return Ok(Value::Boolean(false)); 4569 + } 4570 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4571 + Some(e) => e, 4572 + None => return Ok(Value::Boolean(false)), 4573 + }; 4574 + let count = collection_entry_count(ctx.gc, &ctx.this); 4575 + Ok(Value::Boolean( 4576 + find_key_index(ctx.gc, entries, count, &key).is_some(), 4577 + )) 4578 + } 4579 + 4580 + fn weakmap_proto_delete(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4581 + let key = args.first().cloned().unwrap_or(Value::Undefined); 4582 + if !is_object_value(&key) { 4583 + return Ok(Value::Boolean(false)); 4584 + } 4585 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4586 + Some(e) => e, 4587 + None => return Ok(Value::Boolean(false)), 4588 + }; 4589 + let count = collection_entry_count(ctx.gc, &ctx.this); 4590 + let live = collection_live_count(ctx.gc, &ctx.this); 4591 + if let Some(idx) = find_key_index(ctx.gc, entries, count, &key) { 4592 + delete_entry_at(ctx.gc, entries, idx); 4593 + set_collection_count(ctx.gc, &ctx.this, count, live.saturating_sub(1)); 4594 + return Ok(Value::Boolean(true)); 4595 + } 4596 + Ok(Value::Boolean(false)) 4597 + } 4598 + 4599 + // ── WeakSet constructor & prototype ────────────────────────── 4600 + 4601 + fn weakset_constructor(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4602 + let proto = WEAKSET_PROTO.with(|cell| cell.get()); 4603 + let obj_ref = make_collection_obj(ctx.gc, proto); 4604 + Ok(Value::Object(obj_ref)) 4605 + } 4606 + 4607 + fn init_weakset_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 4608 + let methods: &[NativeMethod] = &[ 4609 + ("add", weakset_proto_add), 4610 + ("has", weakset_proto_has), 4611 + ("delete", weakset_proto_delete), 4612 + ]; 4613 + for &(name, callback) in methods { 4614 + let f = make_native(gc, name, callback); 4615 + set_builtin_prop(gc, proto, name, Value::Function(f)); 4616 + } 4617 + } 4618 + 4619 + fn weakset_proto_add(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4620 + let value = args.first().cloned().unwrap_or(Value::Undefined); 4621 + if !is_object_value(&value) { 4622 + return Err(RuntimeError::type_error("WeakSet value must be an object")); 4623 + } 4624 + set_add_internal(ctx.gc, &ctx.this, value); 4625 + Ok(ctx.this.clone()) 4626 + } 4627 + 4628 + fn weakset_proto_has(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4629 + let value = args.first().cloned().unwrap_or(Value::Undefined); 4630 + if !is_object_value(&value) { 4631 + return Ok(Value::Boolean(false)); 4632 + } 4633 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4634 + Some(e) => e, 4635 + None => return Ok(Value::Boolean(false)), 4636 + }; 4637 + let count = collection_entry_count(ctx.gc, &ctx.this); 4638 + Ok(Value::Boolean( 4639 + find_key_index(ctx.gc, entries, count, &value).is_some(), 4640 + )) 4641 + } 4642 + 4643 + fn weakset_proto_delete(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4644 + let value = args.first().cloned().unwrap_or(Value::Undefined); 4645 + if !is_object_value(&value) { 4646 + return Ok(Value::Boolean(false)); 4647 + } 4648 + let entries = match collection_entries(ctx.gc, &ctx.this) { 4649 + Some(e) => e, 4650 + None => return Ok(Value::Boolean(false)), 4651 + }; 4652 + let count = collection_entry_count(ctx.gc, &ctx.this); 4653 + let live = collection_live_count(ctx.gc, &ctx.this); 4654 + if let Some(idx) = find_key_index(ctx.gc, entries, count, &value) { 4655 + delete_entry_at(ctx.gc, entries, idx); 4656 + set_collection_count(ctx.gc, &ctx.this, count, live.saturating_sub(1)); 4657 + return Ok(Value::Boolean(true)); 4658 + } 4659 + Ok(Value::Boolean(false)) 3815 4660 } 3816 4661 3817 4662 // ── JSON object ──────────────────────────────────────────────
+295
crates/js/src/vm.rs
··· 4521 4521 v => panic!("expected 'xbx', got {v:?}"), 4522 4522 } 4523 4523 } 4524 + 4525 + // ── Map tests ───────────────────────────────────────────── 4526 + 4527 + #[test] 4528 + fn test_map_basic() { 4529 + match eval("var m = new Map(); m.set('a', 1); m.get('a')").unwrap() { 4530 + Value::Number(n) => assert_eq!(n, 1.0), 4531 + v => panic!("expected 1, got {v:?}"), 4532 + } 4533 + } 4534 + 4535 + #[test] 4536 + fn test_map_size() { 4537 + match eval("var m = new Map(); m.set('a', 1); m.set('b', 2); m.size").unwrap() { 4538 + Value::Number(n) => assert_eq!(n, 2.0), 4539 + v => panic!("expected 2, got {v:?}"), 4540 + } 4541 + } 4542 + 4543 + #[test] 4544 + fn test_map_has() { 4545 + match eval("var m = new Map(); m.set('x', 10); m.has('x')").unwrap() { 4546 + Value::Boolean(b) => assert!(b), 4547 + v => panic!("expected true, got {v:?}"), 4548 + } 4549 + match eval("var m = new Map(); m.has('x')").unwrap() { 4550 + Value::Boolean(b) => assert!(!b), 4551 + v => panic!("expected false, got {v:?}"), 4552 + } 4553 + } 4554 + 4555 + #[test] 4556 + fn test_map_delete() { 4557 + match eval("var m = new Map(); m.set('a', 1); m['delete']('a'); m.has('a')").unwrap() { 4558 + Value::Boolean(b) => assert!(!b), 4559 + v => panic!("expected false, got {v:?}"), 4560 + } 4561 + match eval("var m = new Map(); m.set('a', 1); m['delete']('a'); m.size").unwrap() { 4562 + Value::Number(n) => assert_eq!(n, 0.0), 4563 + v => panic!("expected 0, got {v:?}"), 4564 + } 4565 + } 4566 + 4567 + #[test] 4568 + fn test_map_clear() { 4569 + match eval("var m = new Map(); m.set('a', 1); m.set('b', 2); m.clear(); m.size").unwrap() { 4570 + Value::Number(n) => assert_eq!(n, 0.0), 4571 + v => panic!("expected 0, got {v:?}"), 4572 + } 4573 + } 4574 + 4575 + #[test] 4576 + fn test_map_overwrite() { 4577 + match eval("var m = new Map(); m.set('a', 1); m.set('a', 2); m.get('a')").unwrap() { 4578 + Value::Number(n) => assert_eq!(n, 2.0), 4579 + v => panic!("expected 2, got {v:?}"), 4580 + } 4581 + // Size should still be 1 after overwriting. 4582 + match eval("var m = new Map(); m.set('a', 1); m.set('a', 2); m.size").unwrap() { 4583 + Value::Number(n) => assert_eq!(n, 1.0), 4584 + v => panic!("expected 1, got {v:?}"), 4585 + } 4586 + } 4587 + 4588 + #[test] 4589 + fn test_map_get_missing() { 4590 + match eval("var m = new Map(); m.get('missing')").unwrap() { 4591 + Value::Undefined => {} 4592 + v => panic!("expected undefined, got {v:?}"), 4593 + } 4594 + } 4595 + 4596 + #[test] 4597 + fn test_map_chaining() { 4598 + // set() returns the Map for chaining. 4599 + match eval("var m = new Map(); m.set('a', 1).set('b', 2); m.size").unwrap() { 4600 + Value::Number(n) => assert_eq!(n, 2.0), 4601 + v => panic!("expected 2, got {v:?}"), 4602 + } 4603 + } 4604 + 4605 + #[test] 4606 + fn test_map_nan_key() { 4607 + // NaN === NaN for Map keys (SameValueZero). 4608 + match eval("var m = new Map(); m.set(NaN, 'nan'); m.get(NaN)").unwrap() { 4609 + Value::String(s) => assert_eq!(s, "nan"), 4610 + v => panic!("expected 'nan', got {v:?}"), 4611 + } 4612 + } 4613 + 4614 + #[test] 4615 + fn test_map_object_key() { 4616 + match eval("var m = new Map(); var o = {}; m.set(o, 'val'); m.get(o)").unwrap() { 4617 + Value::String(s) => assert_eq!(s, "val"), 4618 + v => panic!("expected 'val', got {v:?}"), 4619 + } 4620 + } 4621 + 4622 + #[test] 4623 + fn test_map_constructor_with_pairs() { 4624 + match eval("var m = new Map([['a', 1], ['b', 2]]); m.get('b')").unwrap() { 4625 + Value::Number(n) => assert_eq!(n, 2.0), 4626 + v => panic!("expected 2, got {v:?}"), 4627 + } 4628 + } 4629 + 4630 + #[test] 4631 + fn test_map_keys_values_entries() { 4632 + match eval("var m = new Map(); m.set('a', 1); m.set('b', 2); m.keys().length").unwrap() { 4633 + Value::Number(n) => assert_eq!(n, 2.0), 4634 + v => panic!("expected 2, got {v:?}"), 4635 + } 4636 + match eval("var m = new Map(); m.set('a', 1); m.set('b', 2); m.values()[1]").unwrap() { 4637 + Value::Number(n) => assert_eq!(n, 2.0), 4638 + v => panic!("expected 2, got {v:?}"), 4639 + } 4640 + match eval("var m = new Map(); m.set('a', 1); m.entries()[0][0]").unwrap() { 4641 + Value::String(s) => assert_eq!(s, "a"), 4642 + v => panic!("expected 'a', got {v:?}"), 4643 + } 4644 + } 4645 + 4646 + #[test] 4647 + fn test_map_insertion_order() { 4648 + match eval("var m = new Map(); m.set('c', 3); m.set('a', 1); m.set('b', 2); m.keys()[0]") 4649 + .unwrap() 4650 + { 4651 + Value::String(s) => assert_eq!(s, "c"), 4652 + v => panic!("expected 'c', got {v:?}"), 4653 + } 4654 + } 4655 + 4656 + // ── Set tests ───────────────────────────────────────────── 4657 + 4658 + #[test] 4659 + fn test_set_basic() { 4660 + match eval("var s = new Set(); s.add(1); s.add(2); s.size").unwrap() { 4661 + Value::Number(n) => assert_eq!(n, 2.0), 4662 + v => panic!("expected 2, got {v:?}"), 4663 + } 4664 + } 4665 + 4666 + #[test] 4667 + fn test_set_has() { 4668 + match eval("var s = new Set(); s.add(42); s.has(42)").unwrap() { 4669 + Value::Boolean(b) => assert!(b), 4670 + v => panic!("expected true, got {v:?}"), 4671 + } 4672 + match eval("var s = new Set(); s.has(42)").unwrap() { 4673 + Value::Boolean(b) => assert!(!b), 4674 + v => panic!("expected false, got {v:?}"), 4675 + } 4676 + } 4677 + 4678 + #[test] 4679 + fn test_set_delete() { 4680 + match eval("var s = new Set(); s.add(1); s['delete'](1); s.has(1)").unwrap() { 4681 + Value::Boolean(b) => assert!(!b), 4682 + v => panic!("expected false, got {v:?}"), 4683 + } 4684 + match eval("var s = new Set(); s.add(1); s['delete'](1); s.size").unwrap() { 4685 + Value::Number(n) => assert_eq!(n, 0.0), 4686 + v => panic!("expected 0, got {v:?}"), 4687 + } 4688 + } 4689 + 4690 + #[test] 4691 + fn test_set_clear() { 4692 + match eval("var s = new Set(); s.add(1); s.add(2); s.clear(); s.size").unwrap() { 4693 + Value::Number(n) => assert_eq!(n, 0.0), 4694 + v => panic!("expected 0, got {v:?}"), 4695 + } 4696 + } 4697 + 4698 + #[test] 4699 + fn test_set_uniqueness() { 4700 + match eval("var s = new Set(); s.add(1); s.add(1); s.add(1); s.size").unwrap() { 4701 + Value::Number(n) => assert_eq!(n, 1.0), 4702 + v => panic!("expected 1, got {v:?}"), 4703 + } 4704 + } 4705 + 4706 + #[test] 4707 + fn test_set_chaining() { 4708 + match eval("var s = new Set(); s.add(1).add(2).add(3); s.size").unwrap() { 4709 + Value::Number(n) => assert_eq!(n, 3.0), 4710 + v => panic!("expected 3, got {v:?}"), 4711 + } 4712 + } 4713 + 4714 + #[test] 4715 + fn test_set_nan() { 4716 + match eval("var s = new Set(); s.add(NaN); s.add(NaN); s.size").unwrap() { 4717 + Value::Number(n) => assert_eq!(n, 1.0), 4718 + v => panic!("expected 1, got {v:?}"), 4719 + } 4720 + match eval("var s = new Set(); s.add(NaN); s.has(NaN)").unwrap() { 4721 + Value::Boolean(b) => assert!(b), 4722 + v => panic!("expected true, got {v:?}"), 4723 + } 4724 + } 4725 + 4726 + #[test] 4727 + fn test_set_constructor_from_array() { 4728 + match eval("var s = new Set([1, 2, 3, 2, 1]); s.size").unwrap() { 4729 + Value::Number(n) => assert_eq!(n, 3.0), 4730 + v => panic!("expected 3, got {v:?}"), 4731 + } 4732 + } 4733 + 4734 + #[test] 4735 + fn test_set_values() { 4736 + match eval("var s = new Set(); s.add('a'); s.add('b'); s.values().length").unwrap() { 4737 + Value::Number(n) => assert_eq!(n, 2.0), 4738 + v => panic!("expected 2, got {v:?}"), 4739 + } 4740 + } 4741 + 4742 + #[test] 4743 + fn test_set_entries() { 4744 + // Set.entries() returns [value, value] pairs. 4745 + match eval("var s = new Set(); s.add('x'); s.entries()[0][0]").unwrap() { 4746 + Value::String(s) => assert_eq!(s, "x"), 4747 + v => panic!("expected 'x', got {v:?}"), 4748 + } 4749 + match eval("var s = new Set(); s.add('x'); s.entries()[0][1]").unwrap() { 4750 + Value::String(s) => assert_eq!(s, "x"), 4751 + v => panic!("expected 'x', got {v:?}"), 4752 + } 4753 + } 4754 + 4755 + #[test] 4756 + fn test_set_insertion_order() { 4757 + match eval("var s = new Set(); s.add('c'); s.add('a'); s.add('b'); s.values()[0]").unwrap() 4758 + { 4759 + Value::String(s) => assert_eq!(s, "c"), 4760 + v => panic!("expected 'c', got {v:?}"), 4761 + } 4762 + } 4763 + 4764 + // ── WeakMap tests ───────────────────────────────────────── 4765 + 4766 + #[test] 4767 + fn test_weakmap_basic() { 4768 + match eval("var wm = new WeakMap(); var o = {}; wm.set(o, 'val'); wm.get(o)").unwrap() { 4769 + Value::String(s) => assert_eq!(s, "val"), 4770 + v => panic!("expected 'val', got {v:?}"), 4771 + } 4772 + } 4773 + 4774 + #[test] 4775 + fn test_weakmap_has_delete() { 4776 + match eval("var wm = new WeakMap(); var o = {}; wm.set(o, 1); wm.has(o)").unwrap() { 4777 + Value::Boolean(b) => assert!(b), 4778 + v => panic!("expected true, got {v:?}"), 4779 + } 4780 + match eval("var wm = new WeakMap(); var o = {}; wm.set(o, 1); wm['delete'](o); wm.has(o)") 4781 + .unwrap() 4782 + { 4783 + Value::Boolean(b) => assert!(!b), 4784 + v => panic!("expected false, got {v:?}"), 4785 + } 4786 + } 4787 + 4788 + #[test] 4789 + fn test_weakmap_rejects_primitive_key() { 4790 + assert!(eval("var wm = new WeakMap(); wm.set('str', 1)").is_err()); 4791 + assert!(eval("var wm = new WeakMap(); wm.set(42, 1)").is_err()); 4792 + } 4793 + 4794 + // ── WeakSet tests ───────────────────────────────────────── 4795 + 4796 + #[test] 4797 + fn test_weakset_basic() { 4798 + match eval("var ws = new WeakSet(); var o = {}; ws.add(o); ws.has(o)").unwrap() { 4799 + Value::Boolean(b) => assert!(b), 4800 + v => panic!("expected true, got {v:?}"), 4801 + } 4802 + } 4803 + 4804 + #[test] 4805 + fn test_weakset_delete() { 4806 + match eval("var ws = new WeakSet(); var o = {}; ws.add(o); ws['delete'](o); ws.has(o)") 4807 + .unwrap() 4808 + { 4809 + Value::Boolean(b) => assert!(!b), 4810 + v => panic!("expected false, got {v:?}"), 4811 + } 4812 + } 4813 + 4814 + #[test] 4815 + fn test_weakset_rejects_primitive() { 4816 + assert!(eval("var ws = new WeakSet(); ws.add('str')").is_err()); 4817 + assert!(eval("var ws = new WeakSet(); ws.add(42)").is_err()); 4818 + } 4524 4819 }
-2
crates/js/tests/test262.rs
··· 114 114 "Int8Array", 115 115 "Int16Array", 116 116 "Int32Array", 117 - "Map", 118 - "Set", 119 117 "SharedArrayBuffer", 120 118 "TypedArray", 121 119 "Uint8Array",