Skip to content
File

Blob: src/rust/jsg-test/tests/local_cast.rs

rust447 lines
1// Copyright (c) 2026 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5//! Tests for `impl_local_cast!` conversions added for Float/BigInt typed arrays,
6//! and upcasts to Object (Function -> Object, Array -> Object, `TypedArray` -> Object).
7 
8use jsg::ToJS;
9use jsg::v8;
10 
11// =============================================================================
12// Float/BigInt typed arrays -> Value
13// =============================================================================
14 
15/// `Local<Float32Array>` → `Local<Value>` via `Into`, preserving the type tag.
16#[test]
17fn float32_array_into_value() {
18 let harness = crate::Harness::new();
19 harness.run_in_context(|lock, _ctx| {
20 let data: Vec<f32> = vec![1.0, 2.0, 3.0];
21 let js_val = data.to_js(lock);
22 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
23 let typed: v8::Local<'_, v8::Float32Array> =
24 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
25 assert_eq!(typed.len(), 3);
26 let value: v8::Local<v8::Value> = typed.into();
27 assert!(value.is_float32_array());
28 assert!(value.is_object());
29 assert!(!value.is_float64_array());
30 Ok(())
31 });
32}
33 
34/// `Local<Float64Array>` → `Local<Value>` via `Into`, preserving the type tag.
35#[test]
36fn float64_array_into_value() {
37 let harness = crate::Harness::new();
38 harness.run_in_context(|lock, _ctx| {
39 let data: Vec<f64> = vec![1.0, 2.0, 3.0];
40 let js_val = data.to_js(lock);
41 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
42 let typed: v8::Local<'_, v8::Float64Array> =
43 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
44 assert_eq!(typed.len(), 3);
45 let value: v8::Local<v8::Value> = typed.into();
46 assert!(value.is_float64_array());
47 assert!(value.is_object());
48 assert!(!value.is_float32_array());
49 Ok(())
50 });
51}
52 
53/// `Local<BigInt64Array>` → `Local<Value>` via `Into`, preserving the type tag.
54#[test]
55fn bigint64_array_into_value() {
56 let harness = crate::Harness::new();
57 harness.run_in_context(|lock, _ctx| {
58 let data: Vec<i64> = vec![1, 2, 3];
59 let js_val = data.to_js(lock);
60 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
61 let typed: v8::Local<'_, v8::BigInt64Array> =
62 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
63 assert_eq!(typed.len(), 3);
64 let value: v8::Local<v8::Value> = typed.into();
65 assert!(value.is_bigint64_array());
66 assert!(value.is_object());
67 assert!(!value.is_biguint64_array());
68 Ok(())
69 });
70}
71 
72/// `Local<BigUint64Array>` → `Local<Value>` via `Into`, preserving the type tag.
73#[test]
74fn biguint64_array_into_value() {
75 let harness = crate::Harness::new();
76 harness.run_in_context(|lock, _ctx| {
77 let data: Vec<u64> = vec![1, 2, 3];
78 let js_val = data.to_js(lock);
79 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
80 let typed: v8::Local<'_, v8::BigUint64Array> =
81 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
82 assert_eq!(typed.len(), 3);
83 let value: v8::Local<v8::Value> = typed.into();
84 assert!(value.is_biguint64_array());
85 assert!(value.is_object());
86 assert!(!value.is_bigint64_array());
87 Ok(())
88 });
89}
90 
91// =============================================================================
92// Float/BigInt typed arrays -> TypedArray
93// =============================================================================
94 
95/// `Local<Float32Array>` → `Local<TypedArray>` preserves length.
96#[test]
97fn float32_array_into_typed_array() {
98 let harness = crate::Harness::new();
99 harness.run_in_context(|lock, _ctx| {
100 let data: Vec<f32> = vec![1.5, 2.5];
101 let js_val = data.to_js(lock);
102 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
103 let typed: v8::Local<'_, v8::Float32Array> =
104 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
105 assert_eq!(typed.len(), 2);
106 let ta: v8::Local<v8::TypedArray> = typed.into();
107 assert_eq!(ta.len(), 2);
108 assert!(!ta.is_empty());
109 // Round-trip back and verify elements
110 let back: v8::Local<v8::Float32Array> = ta.into();
111 assert!((back.get(0) - 1.5).abs() < f32::EPSILON);
112 assert!((back.get(1) - 2.5).abs() < f32::EPSILON);
113 Ok(())
114 });
115}
116 
117/// `Local<Float64Array>` → `Local<TypedArray>` preserves length.
118#[test]
119fn float64_array_into_typed_array() {
120 let harness = crate::Harness::new();
121 harness.run_in_context(|lock, _ctx| {
122 let data: Vec<f64> = vec![1.5, 2.5];
123 let js_val = data.to_js(lock);
124 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
125 let typed: v8::Local<'_, v8::Float64Array> =
126 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
127 assert_eq!(typed.len(), 2);
128 let ta: v8::Local<v8::TypedArray> = typed.into();
129 assert_eq!(ta.len(), 2);
130 assert!(!ta.is_empty());
131 // Round-trip back and verify elements
132 let back: v8::Local<v8::Float64Array> = ta.into();
133 assert!((back.get(0) - 1.5).abs() < f64::EPSILON);
134 assert!((back.get(1) - 2.5).abs() < f64::EPSILON);
135 Ok(())
136 });
137}
138 
139/// `Local<BigInt64Array>` → `Local<TypedArray>` preserves length.
140#[test]
141fn bigint64_array_into_typed_array() {
142 let harness = crate::Harness::new();
143 harness.run_in_context(|lock, _ctx| {
144 let data: Vec<i64> = vec![10, -20];
145 let js_val = data.to_js(lock);
146 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
147 let typed: v8::Local<'_, v8::BigInt64Array> =
148 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
149 assert_eq!(typed.len(), 2);
150 let ta: v8::Local<v8::TypedArray> = typed.into();
151 assert_eq!(ta.len(), 2);
152 assert!(!ta.is_empty());
153 // Round-trip back and verify elements
154 let back: v8::Local<v8::BigInt64Array> = ta.into();
155 assert_eq!(back.get(0), 10);
156 assert_eq!(back.get(1), -20);
157 Ok(())
158 });
159}
160 
161/// `Local<BigUint64Array>` → `Local<TypedArray>` preserves length.
162#[test]
163fn biguint64_array_into_typed_array() {
164 let harness = crate::Harness::new();
165 harness.run_in_context(|lock, _ctx| {
166 let data: Vec<u64> = vec![10, 20];
167 let js_val = data.to_js(lock);
168 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
169 let typed: v8::Local<'_, v8::BigUint64Array> =
170 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
171 assert_eq!(typed.len(), 2);
172 let ta: v8::Local<v8::TypedArray> = typed.into();
173 assert_eq!(ta.len(), 2);
174 assert!(!ta.is_empty());
175 // Round-trip back and verify elements
176 let back: v8::Local<v8::BigUint64Array> = ta.into();
177 assert_eq!(back.get(0), 10);
178 assert_eq!(back.get(1), 20);
179 Ok(())
180 });
181}
182 
183// =============================================================================
184// Upcasts to Object
185// =============================================================================
186 
187/// `Local<Function>` → `Local<Object>` via `Into`; verify `has()` sees `name` property.
188#[test]
189fn function_into_object() {
190 let harness = crate::Harness::new();
191 harness.run_in_context(|lock, ctx| {
192 let func_val = ctx.eval_raw("(function myFunc() {})").unwrap();
193 assert!(func_val.is_function());
194 let func = func_val.try_as::<v8::Function>().unwrap();
195 let obj: v8::Local<v8::Object> = func.into();
196 // Functions are objects — the `name` property should be accessible.
197 assert!(obj.has(lock, "name"));
198 let prop = obj.get(lock, "name").unwrap();
199 let name: String = <String as jsg::FromJS>::from_js(lock, prop)?;
200 assert_eq!(name, "myFunc");
201 Ok(())
202 });
203}
204 
205/// `Local<Array>` → `Local<Object>` via `Into`; verify `length` property preserved.
206#[test]
207fn array_into_object() {
208 let harness = crate::Harness::new();
209 harness.run_in_context(|lock, ctx| {
210 let arr_val = ctx.eval_raw("[1, 2, 3]").unwrap();
211 assert!(arr_val.is_array());
212 let arr = arr_val.try_as::<v8::Array>().unwrap();
213 assert_eq!(arr.len(), 3);
214 let obj: v8::Local<v8::Object> = arr.into();
215 assert!(obj.has(lock, "length"));
216 let prop = obj.get(lock, "length").unwrap();
217 let len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?;
218 assert!((len.value() - 3.0).abs() < f64::EPSILON);
219 Ok(())
220 });
221}
222 
223/// `Local<TypedArray>` → `Local<Object>` via `Into`; verify `byteLength` property.
224#[test]
225fn typed_array_into_object() {
226 let harness = crate::Harness::new();
227 harness.run_in_context(|lock, _ctx| {
228 let data: Vec<u8> = vec![1, 2, 3];
229 let js_val = data.to_js(lock);
230 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
231 let typed: v8::Local<'_, v8::Uint8Array> =
232 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
233 assert_eq!(typed.len(), 3);
234 // Uint8Array -> TypedArray -> Object
235 let ta: v8::Local<v8::TypedArray> = typed.into();
236 assert_eq!(ta.len(), 3);
237 let obj: v8::Local<v8::Object> = ta.into();
238 assert!(obj.has(lock, "byteLength"));
239 let prop = obj.get(lock, "byteLength").unwrap();
240 let byte_len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?;
241 assert!((byte_len.value() - 3.0).abs() < f64::EPSILON);
242 Ok(())
243 });
244}
245 
246// =============================================================================
247// Property access on Object obtained from upcast
248// =============================================================================
249 
250/// Upcast `Function` to `Object`, then read a custom property via `.get()`.
251/// Functions are objects in JS, so custom properties can be set on them.
252#[test]
253fn function_as_object_property_access() {
254 let harness = crate::Harness::new();
255 harness.run_in_context(|lock, ctx| {
256 let func_val = ctx
257 .eval_raw("var f = () => {}; f.custom = 'hello'; f")
258 .unwrap();
259 let func = func_val.try_as::<v8::Function>().unwrap();
260 let obj: v8::Local<v8::Object> = func.into();
261 
262 assert!(obj.has(lock, "custom"));
263 let prop = obj.get(lock, "custom").unwrap();
264 let val: String = <String as jsg::FromJS>::from_js(lock, prop)?;
265 assert_eq!(val, "hello");
266 Ok(())
267 });
268}
269 
270/// Upcast `Array` to `Object`, then read `length` via `.get()`.
271#[test]
272fn array_as_object_property_access() {
273 let harness = crate::Harness::new();
274 harness.run_in_context(|lock, ctx| {
275 let arr_val = ctx.eval_raw("[10, 20, 30]").unwrap();
276 let arr = arr_val.try_as::<v8::Array>().unwrap();
277 let obj: v8::Local<v8::Object> = arr.into();
278 
279 assert!(obj.has(lock, "length"));
280 let prop = obj.get(lock, "length").unwrap();
281 let val: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?;
282 assert!((val.value() - 3.0).abs() < f64::EPSILON);
283 Ok(())
284 });
285}
286 
287/// Upcast `TypedArray` (via `Uint8Array`) to `Object`, then read `byteLength`.
288#[test]
289fn typed_array_as_object_property_access() {
290 let harness = crate::Harness::new();
291 harness.run_in_context(|lock, _ctx| {
292 let data: Vec<u8> = vec![1, 2, 3, 4, 5];
293 let js_val = data.to_js(lock);
294 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
295 let typed: v8::Local<'_, v8::Uint8Array> =
296 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
297 let ta: v8::Local<v8::TypedArray> = typed.into();
298 let obj: v8::Local<v8::Object> = ta.into();
299 
300 assert!(obj.has(lock, "byteLength"));
301 assert!(obj.has(lock, "length"));
302 let prop = obj.get(lock, "byteLength").unwrap();
303 let byte_len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?;
304 assert!((byte_len.value() - 5.0).abs() < f64::EPSILON);
305 let prop = obj.get(lock, "length").unwrap();
306 let len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?;
307 assert!((len.value() - 5.0).abs() < f64::EPSILON);
308 Ok(())
309 });
310}
311 
312// =============================================================================
313// Bidirectional casts (downcast from Value back to concrete type)
314// =============================================================================
315 
316/// Round-trip: `Float32Array` → `Value` → `Float32Array`; verify elements preserved.
317#[test]
318fn float32_array_value_roundtrip() {
319 let harness = crate::Harness::new();
320 harness.run_in_context(|lock, _ctx| {
321 let data: Vec<f32> = vec![1.5, 2.5, 3.5];
322 let js_val = data.to_js(lock);
323 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
324 let typed: v8::Local<'_, v8::Float32Array> =
325 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
326 assert_eq!(typed.len(), 3);
327 let value: v8::Local<v8::Value> = typed.into();
328 assert!(value.is_float32_array());
329 let back: v8::Local<v8::Float32Array> = value.into();
330 assert_eq!(back.len(), 3);
331 assert!((back.get(0) - 1.5).abs() < f32::EPSILON);
332 assert!((back.get(1) - 2.5).abs() < f32::EPSILON);
333 assert!((back.get(2) - 3.5).abs() < f32::EPSILON);
334 Ok(())
335 });
336}
337 
338/// Round-trip: `Float64Array` → `Value` → `Float64Array`; verify elements preserved.
339#[test]
340fn float64_array_value_roundtrip() {
341 let harness = crate::Harness::new();
342 harness.run_in_context(|lock, _ctx| {
343 let data: Vec<f64> = vec![1.1, 2.2, 3.3];
344 let js_val = data.to_js(lock);
345 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
346 let typed: v8::Local<'_, v8::Float64Array> =
347 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
348 assert_eq!(typed.len(), 3);
349 let value: v8::Local<v8::Value> = typed.into();
350 assert!(value.is_float64_array());
351 let back: v8::Local<v8::Float64Array> = value.into();
352 assert_eq!(back.len(), 3);
353 assert!((back.get(0) - 1.1).abs() < f64::EPSILON);
354 assert!((back.get(1) - 2.2).abs() < f64::EPSILON);
355 assert!((back.get(2) - 3.3).abs() < f64::EPSILON);
356 Ok(())
357 });
358}
359 
360/// Round-trip: `BigInt64Array` → `Value` → `BigInt64Array`; verify elements preserved.
361#[test]
362fn bigint64_array_value_roundtrip() {
363 let harness = crate::Harness::new();
364 harness.run_in_context(|lock, _ctx| {
365 let data: Vec<i64> = vec![-100, 0, 100];
366 let js_val = data.to_js(lock);
367 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
368 let typed: v8::Local<'_, v8::BigInt64Array> =
369 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
370 assert_eq!(typed.len(), 3);
371 let value: v8::Local<v8::Value> = typed.into();
372 assert!(value.is_bigint64_array());
373 let back: v8::Local<v8::BigInt64Array> = value.into();
374 assert_eq!(back.len(), 3);
375 assert_eq!(back.get(0), -100);
376 assert_eq!(back.get(1), 0);
377 assert_eq!(back.get(2), 100);
378 Ok(())
379 });
380}
381 
382/// Round-trip: `BigUint64Array` → `Value` → `BigUint64Array`; verify elements preserved.
383#[test]
384fn biguint64_array_value_roundtrip() {
385 let harness = crate::Harness::new();
386 harness.run_in_context(|lock, _ctx| {
387 let data: Vec<u64> = vec![0, 42, 999];
388 let js_val = data.to_js(lock);
389 // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.
390 let typed: v8::Local<'_, v8::BigUint64Array> =
391 unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };
392 assert_eq!(typed.len(), 3);
393 let value: v8::Local<v8::Value> = typed.into();
394 assert!(value.is_biguint64_array());
395 let back: v8::Local<v8::BigUint64Array> = value.into();
396 assert_eq!(back.len(), 3);
397 assert_eq!(back.get(0), 0);
398 assert_eq!(back.get(1), 42);
399 assert_eq!(back.get(2), 999);
400 Ok(())
401 });
402}
403 
404/// Round-trip: `Function` → `Object` → `Function`; verify callable after roundtrip.
405#[test]
406fn function_object_roundtrip() {
407 let harness = crate::Harness::new();
408 harness.run_in_context(|lock, ctx| {
409 let func_val = ctx.eval_raw("(function namedFn() { return 42; })").unwrap();
410 assert!(func_val.is_function());
411 let func = func_val.try_as::<v8::Function>().unwrap();
412 let obj: v8::Local<v8::Object> = func.into();
413 // Verify function-specific property is readable as Object
414 assert!(obj.has(lock, "name"));
415 let prop = obj.get(lock, "name").unwrap();
416 let name: String = <String as jsg::FromJS>::from_js(lock, prop)?;
417 assert_eq!(name, "namedFn");
418 // Roundtrip back and verify it's still callable
419 let back: v8::Local<v8::Function> = obj.into();
420 let result = back.call::<jsg::Number, v8::Local<v8::Value>>(lock, None, &[])?;
421 assert!((result.value() - 42.0).abs() < f64::EPSILON);
422 Ok(())
423 });
424}
425 
426/// Round-trip: `Array` → `Object` → `Array`; verify length preserved.
427#[test]
428fn array_object_roundtrip() {
429 let harness = crate::Harness::new();
430 harness.run_in_context(|lock, ctx| {
431 let arr_val = ctx.eval_raw("[10, 20, 30]").unwrap();
432 assert!(arr_val.is_array());
433 let arr = arr_val.try_as::<v8::Array>().unwrap();
434 assert_eq!(arr.len(), 3);
435 let obj: v8::Local<v8::Object> = arr.into();
436 // Verify length via Object property access
437 let prop = obj.get(lock, "length").unwrap();
438 let len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?;
439 assert!((len.value() - 3.0).abs() < f64::EPSILON);
440 // Roundtrip back and verify Array-specific methods work
441 let back: v8::Local<v8::Array> = obj.into();
442 assert_eq!(back.len(), 3);
443 assert!(!back.is_empty());
444 Ok(())
445 });
446}