@tishlang/tish-format 2.39.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +4 -1
- package/bin/tish-format +0 -0
- package/crates/tish/src/cli_help.rs +28 -0
- package/crates/tish/src/main.rs +41 -5
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +21 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/tish.d.tish +4 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +5 -1
- package/crates/tish/tests/regr_typed_externs.rs +59 -0
- package/crates/tish/tests/scope_merge.rs +79 -0
- package/crates/tish_build_utils/src/lib.rs +27 -1
- package/crates/tish_builtins/Cargo.toml +14 -6
- package/crates/tish_builtins/src/array.rs +130 -54
- package/crates/tish_builtins/src/collections.rs +9 -5
- package/crates/tish_builtins/src/construct.rs +7 -1
- package/crates/tish_builtins/src/date.rs +15 -1
- package/crates/tish_builtins/src/globals.rs +65 -3
- package/crates/tish_builtins/src/helpers.rs +7 -3
- package/crates/tish_builtins/src/iterator.rs +5 -1
- package/crates/tish_builtins/src/lib.rs +3 -0
- package/crates/tish_builtins/src/math.rs +14 -0
- package/crates/tish_builtins/src/number.rs +72 -6
- package/crates/tish_builtins/src/object.rs +6 -1
- package/crates/tish_builtins/src/string.rs +13 -2
- package/crates/tish_builtins/src/symbol.rs +17 -17
- package/crates/tish_builtins/src/typedarrays.rs +9 -2
- package/crates/tish_bytecode/src/compiler.rs +76 -2
- package/crates/tish_bytecode/src/lib.rs +1 -1
- package/crates/tish_bytecode/src/opcode.rs +77 -2
- package/crates/tish_compile/src/codegen.rs +2154 -225
- package/crates/tish_compile/src/infer.rs +37 -41
- package/crates/tish_compile/src/lib.rs +13 -1
- package/crates/tish_compile/src/platform_resolve.rs +361 -0
- package/crates/tish_compile/src/resolve.rs +195 -11
- package/crates/tish_compile/src/schemes.rs +333 -0
- package/crates/tish_compile/src/types.rs +205 -34
- package/crates/tish_compile/tests/perf_codegen_320.rs +2 -2
- package/crates/tish_compile/tests/perf_codegen_module_const_forof.rs +3 -1
- package/crates/tish_compile/tests/platform_resolve_cli.rs +164 -0
- package/crates/tish_compile/tests/regr_556_param_name_collision.rs +40 -0
- package/crates/tish_compile/tests/regr_558_struct_array_write.rs +65 -0
- package/crates/tish_compile/tests/regr_558b_struct_field_read.rs +21 -0
- package/crates/tish_compile/tests/regr_558c_wrapped_struct_array.rs +64 -0
- package/crates/tish_compile/tests/regr_558d_native_vec_mutators.rs +156 -0
- package/crates/tish_compile/tests/regr_562_wrapped_struct_array_push.rs +50 -0
- package/crates/tish_compile/tests/regr_fixed_native_coercions.rs +69 -0
- package/crates/tish_compile/tests/regr_gba_numerics_gated.rs +58 -0
- package/crates/tish_compile/tests/regr_gba_struct_reflection.rs +106 -0
- package/crates/tish_compile/tests/regression/name_collision_lib.tish +6 -0
- package/crates/tish_compile/tests/regression/name_collision_main.tish +13 -0
- package/crates/tish_compile/tests/regression/struct_array_write.tish +22 -0
- package/crates/tish_compile/tests/regression/struct_field_return_read.tish +10 -0
- package/crates/tish_compiler_wasm/Cargo.toml +1 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +56 -6
- package/crates/tish_core/Cargo.toml +39 -4
- package/crates/tish_core/src/compat.rs +506 -0
- package/crates/tish_core/src/console_style.rs +23 -1
- package/crates/tish_core/src/json.rs +42 -10
- package/crates/tish_core/src/lib.rs +121 -25
- package/crates/tish_core/src/macros.rs +1 -1
- package/crates/tish_core/src/shape.rs +4 -2
- package/crates/tish_core/src/uri.rs +8 -1
- package/crates/tish_core/src/value.rs +305 -54
- package/crates/tish_core/src/vmref.rs +17 -5
- package/crates/tish_eval/Cargo.toml +2 -0
- package/crates/tish_eval/src/eval.rs +90 -3
- package/crates/tish_eval/src/value.rs +8 -0
- package/crates/tish_eval/src/value_convert.rs +316 -21
- package/crates/tish_fmt/Cargo.toml +1 -1
- package/crates/tish_lsp/Cargo.toml +2 -0
- package/crates/tish_lsp/src/import_goto.rs +41 -14
- package/crates/tish_lsp/src/main.rs +18 -0
- package/crates/tish_native/src/build.rs +246 -0
- package/crates/tish_native/src/config.rs +20 -0
- package/crates/tish_native/src/lib.rs +16 -6
- package/crates/tish_parser/src/lib.rs +28 -0
- package/crates/tish_parser/src/parser.rs +25 -4
- package/crates/tish_runtime/src/lib.rs +55 -9
- package/crates/tish_runtime_gba/Cargo.lock +837 -0
- package/crates/tish_runtime_gba/Cargo.toml +16 -0
- package/crates/tish_runtime_gba/src/gba.rs +211 -0
- package/crates/tish_runtime_gba/src/lib.rs +804 -0
- package/crates/tish_vm/src/jit.rs +1446 -164
- package/crates/tish_vm/src/vm.rs +583 -173
- package/justfile +9 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- package/platform/win32-x64/tish-fmt.exe +0 -0
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
//! Unified Value type for Tish runtime values.
|
|
2
2
|
|
|
3
|
-
use
|
|
4
|
-
use std::sync::Arc;
|
|
5
|
-
|
|
6
|
-
use ahash::AHashMap;
|
|
3
|
+
use crate::compat::{AHashMap, ArcStr, AtomicU64, Arc, Ordering, RandomState};
|
|
7
4
|
use indexmap::IndexMap;
|
|
8
5
|
use smallvec::SmallVec;
|
|
9
6
|
|
|
7
|
+
#[cfg(feature = "portable")]
|
|
8
|
+
use alloc::{
|
|
9
|
+
boxed::Box,
|
|
10
|
+
format,
|
|
11
|
+
string::{String, ToString},
|
|
12
|
+
vec,
|
|
13
|
+
vec::Vec,
|
|
14
|
+
};
|
|
15
|
+
#[cfg(feature = "portable")]
|
|
16
|
+
use crate::compat::FloatExt;
|
|
17
|
+
|
|
10
18
|
use crate::vmref::VmRef;
|
|
11
19
|
|
|
12
20
|
/// Property map for objects and other `Arc<str>` → `Value` tables (VM globals, scopes).
|
|
@@ -75,12 +83,12 @@ use fancy_regex::Regex;
|
|
|
75
83
|
pub trait Callable: Send + Sync {
|
|
76
84
|
fn call(&self, args: &[Value]) -> Value;
|
|
77
85
|
/// Downcast hook for the VM frame path; native adapters return themselves (downcast fails).
|
|
78
|
-
fn as_any(&self) -> &dyn
|
|
86
|
+
fn as_any(&self) -> &dyn core::any::Any;
|
|
79
87
|
}
|
|
80
88
|
#[cfg(not(feature = "send-values"))]
|
|
81
89
|
pub trait Callable {
|
|
82
90
|
fn call(&self, args: &[Value]) -> Value;
|
|
83
|
-
fn as_any(&self) -> &dyn
|
|
91
|
+
fn as_any(&self) -> &dyn core::any::Any;
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
/// Adapter wrapping a plain `Fn` closure (every native builtin) as a [`Callable`].
|
|
@@ -91,7 +99,7 @@ impl<F: Fn(&[Value]) -> Value + Send + Sync + 'static> Callable for FnCallable<F
|
|
|
91
99
|
fn call(&self, args: &[Value]) -> Value {
|
|
92
100
|
(self.0)(args)
|
|
93
101
|
}
|
|
94
|
-
fn as_any(&self) -> &dyn
|
|
102
|
+
fn as_any(&self) -> &dyn core::any::Any {
|
|
95
103
|
self
|
|
96
104
|
}
|
|
97
105
|
}
|
|
@@ -101,7 +109,7 @@ impl<F: Fn(&[Value]) -> Value + 'static> Callable for FnCallable<F> {
|
|
|
101
109
|
fn call(&self, args: &[Value]) -> Value {
|
|
102
110
|
(self.0)(args)
|
|
103
111
|
}
|
|
104
|
-
fn as_any(&self) -> &dyn
|
|
112
|
+
fn as_any(&self) -> &dyn core::any::Any {
|
|
105
113
|
self
|
|
106
114
|
}
|
|
107
115
|
}
|
|
@@ -109,7 +117,7 @@ impl<F: Fn(&[Value]) -> Value + 'static> Callable for FnCallable<F> {
|
|
|
109
117
|
#[cfg(feature = "send-values")]
|
|
110
118
|
pub type NativeFn = Arc<dyn Callable>;
|
|
111
119
|
#[cfg(not(feature = "send-values"))]
|
|
112
|
-
pub type NativeFn =
|
|
120
|
+
pub type NativeFn = alloc::rc::Rc<dyn Callable>;
|
|
113
121
|
|
|
114
122
|
/// Build a raw [`NativeFn`] from a plain closure (wraps it in [`FnCallable`]). For sites that
|
|
115
123
|
/// need a `NativeFn` handle directly rather than a `Value::Function` (e.g. HTTP/promise/timer
|
|
@@ -120,7 +128,7 @@ pub fn native_fn<F: Fn(&[Value]) -> Value + Send + Sync + 'static>(f: F) -> Nati
|
|
|
120
128
|
}
|
|
121
129
|
#[cfg(not(feature = "send-values"))]
|
|
122
130
|
pub fn native_fn<F: Fn(&[Value]) -> Value + 'static>(f: F) -> NativeFn {
|
|
123
|
-
|
|
131
|
+
alloc::rc::Rc::new(FnCallable(f))
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
/// Trait for opaque Rust types exposed to Tish (e.g. Polars DataFrame).
|
|
@@ -141,7 +149,7 @@ pub trait TishOpaque: Send + Sync {
|
|
|
141
149
|
fn get_method(&self, name: &str) -> Option<NativeFn>;
|
|
142
150
|
|
|
143
151
|
/// For downcasting `Arc<dyn TishOpaque>` in native crates (e.g. Polars → `DataFrame`).
|
|
144
|
-
fn as_any(&self) -> &dyn
|
|
152
|
+
fn as_any(&self) -> &dyn core::any::Any;
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
/// Single-threaded variant (no `Send + Sync` bound); see the `send-values` doc above.
|
|
@@ -154,13 +162,42 @@ pub trait TishOpaque {
|
|
|
154
162
|
fn get_method(&self, name: &str) -> Option<NativeFn>;
|
|
155
163
|
|
|
156
164
|
/// For downcasting `Arc<dyn TishOpaque>` in native crates (e.g. Polars → `DataFrame`).
|
|
157
|
-
fn as_any(&self) -> &dyn
|
|
165
|
+
fn as_any(&self) -> &dyn core::any::Any;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// A typed native struct (from an `interface` / typed object literal in native codegen) carried inside
|
|
169
|
+
/// a [`Value::Struct`] WITHOUT serialising to a hashmap object. Each generated `TishStruct_*` implements
|
|
170
|
+
/// it: a boxed function that returns/passes a typed struct keeps it by reference, property access
|
|
171
|
+
/// dispatches here (a small `match` on the field name — no string hash, no allocation), and it
|
|
172
|
+
/// materialises to a real `Value::Object` only when a boxed `Value::Struct` (a struct whose static
|
|
173
|
+
/// type was erased) crosses into genuinely dynamic territory (`JSON.stringify`, display,
|
|
174
|
+
/// `Object.keys`/`values`/`entries`, `for..in`, `in`, `Object.assign`, object-literal spread `{...s}`).
|
|
175
|
+
/// This is the RUNTIME fallback: when the struct's static type IS known at a reflection site, GBA
|
|
176
|
+
/// codegen lowers `Object.keys`/`values`/`entries`/`in`/`{...s}` to native field reads instead — the
|
|
177
|
+
/// struct is never boxed and this materialisation never runs. No `Send + Sync` bound: the `Value::Struct` variant is
|
|
178
|
+
/// `cfg(portable)` and `portable` excludes `send-values` (they are mutually exclusive), so the ref is
|
|
179
|
+
/// single-threaded `Rc` — mirroring the single-threaded [`TishOpaque`].
|
|
180
|
+
pub trait TishStruct {
|
|
181
|
+
/// Read a field as a boxed `Value` (`Null` if `key` is not a field). Backs `get_prop`.
|
|
182
|
+
fn tish_get(&self, key: &str) -> Value;
|
|
183
|
+
/// Write a field from a boxed `Value` (no-op if `key` is not a field). Backs `set_prop`.
|
|
184
|
+
fn tish_set(&mut self, key: &str, value: Value);
|
|
185
|
+
/// Materialise an ordered `Value::Object` copy (field-declaration order, JS insertion order). Used
|
|
186
|
+
/// for JSON / display / `Object`-interop, and as the non-portable `Value::from_struct` fallback so
|
|
187
|
+
/// off-GBA backends behave EXACTLY as the historical `object_from_pairs` boundary did.
|
|
188
|
+
fn tish_to_object(&self) -> Value;
|
|
189
|
+
/// `typeof` result — always "object".
|
|
190
|
+
fn tish_type_name(&self) -> &'static str {
|
|
191
|
+
"object"
|
|
192
|
+
}
|
|
193
|
+
/// Downcast hook — recover the concrete `TishStruct_*` (e.g. for a future typed fast path).
|
|
194
|
+
fn as_any(&self) -> &dyn core::any::Any;
|
|
158
195
|
}
|
|
159
196
|
|
|
160
197
|
/// Trait for Promise-like values that can be awaited (block until settled).
|
|
161
198
|
/// Implemented by the runtime for native compile; interpreter uses its own Promise.
|
|
162
199
|
pub trait TishPromise: Send + Sync {
|
|
163
|
-
fn block_until_settled(&self) ->
|
|
200
|
+
fn block_until_settled(&self) -> core::result::Result<Value, Value>;
|
|
164
201
|
/// Try to settle WITHOUT blocking. Returns `Some(result)` if the promise was already
|
|
165
202
|
/// settled before this call; returns `None` if it is still pending (a background thread
|
|
166
203
|
/// / I/O task has not completed yet). Default: always pending — implementors of async
|
|
@@ -169,7 +206,7 @@ pub trait TishPromise: Send + Sync {
|
|
|
169
206
|
/// Used by `race`/`any`/`allSettled` to handle already-settled promises in input-order
|
|
170
207
|
/// (deterministic, JS-compatible) before falling back to concurrent thread waiting for
|
|
171
208
|
/// genuinely-pending ones.
|
|
172
|
-
fn try_settle(&self) -> Option<
|
|
209
|
+
fn try_settle(&self) -> Option<core::result::Result<Value, Value>> {
|
|
173
210
|
None
|
|
174
211
|
}
|
|
175
212
|
}
|
|
@@ -236,8 +273,8 @@ impl RegExpFlags {
|
|
|
236
273
|
}
|
|
237
274
|
|
|
238
275
|
#[cfg(feature = "regex")]
|
|
239
|
-
impl
|
|
240
|
-
fn fmt(&self, f: &mut
|
|
276
|
+
impl core::fmt::Display for RegExpFlags {
|
|
277
|
+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
241
278
|
if self.global {
|
|
242
279
|
f.write_str("g")?;
|
|
243
280
|
}
|
|
@@ -344,20 +381,111 @@ impl TishRegExp {
|
|
|
344
381
|
/// **Thread safety**: `Value: Send + Sync`. Mutable payloads live inside
|
|
345
382
|
/// [`VmRef`], a `Send + Sync` `Arc<Mutex<T>>` wrapper that preserves the
|
|
346
383
|
/// `RefCell`-style borrow API. Functions are `Arc<dyn Fn + Send + Sync>`.
|
|
384
|
+
/// Backing store for a [`Value::NumberArray`] (#199 packed arrays). Starts `Packed`; a mutation
|
|
385
|
+
/// that would introduce a non-numeric element upgrades it to `Boxed` **in place** via
|
|
386
|
+
/// [`NumArrayBacking::deopt`], so all aliases sharing the `VmRef` observe the change (#506).
|
|
387
|
+
///
|
|
388
|
+
/// Access rules that keep the two arms sound at every call site:
|
|
389
|
+
/// - single-element read → [`get`](Self::get) (O(1), no alloc);
|
|
390
|
+
/// - iterate/snapshot as `Value`s → [`to_values`](Self::to_values) (Packed boxes each f64, same
|
|
391
|
+
/// cost the old `materialize` path paid);
|
|
392
|
+
/// - hot packed-only fast path (sort/fold/`sum`) → [`as_packed`](Self::as_packed) /
|
|
393
|
+
/// [`as_packed_mut`](Self::as_packed_mut), which return `None` after a deopt so the caller falls
|
|
394
|
+
/// back to the boxed path instead of assuming `Vec<f64>`.
|
|
395
|
+
#[derive(Clone, Debug)]
|
|
396
|
+
pub enum NumArrayBacking {
|
|
397
|
+
Packed(Vec<f64>),
|
|
398
|
+
Boxed(Vec<Value>),
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
impl NumArrayBacking {
|
|
402
|
+
#[inline]
|
|
403
|
+
pub fn len(&self) -> usize {
|
|
404
|
+
match self {
|
|
405
|
+
NumArrayBacking::Packed(v) => v.len(),
|
|
406
|
+
NumArrayBacking::Boxed(v) => v.len(),
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
#[inline]
|
|
410
|
+
pub fn is_empty(&self) -> bool {
|
|
411
|
+
self.len() == 0
|
|
412
|
+
}
|
|
413
|
+
/// Element `i` as a `Value` (Packed: `Number(n)`, NaN kept as `Number(NaN)` — the hole-as-null
|
|
414
|
+
/// rule is applied by the specific read sites that want it, preserving current per-site
|
|
415
|
+
/// behavior). `None` if out of bounds.
|
|
416
|
+
#[inline]
|
|
417
|
+
pub fn get(&self, i: usize) -> Option<Value> {
|
|
418
|
+
match self {
|
|
419
|
+
NumArrayBacking::Packed(v) => v.get(i).map(|n| Value::Number(*n)),
|
|
420
|
+
NumArrayBacking::Boxed(v) => v.get(i).cloned(),
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/// Snapshot the whole array as boxed `Value`s (Packed boxes each `f64`).
|
|
424
|
+
#[inline]
|
|
425
|
+
pub fn to_values(&self) -> Vec<Value> {
|
|
426
|
+
match self {
|
|
427
|
+
NumArrayBacking::Packed(v) => v.iter().map(|n| Value::Number(*n)).collect(),
|
|
428
|
+
NumArrayBacking::Boxed(v) => v.clone(),
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/// The packed `f64` slice, iff still packed (hot fast paths); `None` once deopted.
|
|
432
|
+
#[inline]
|
|
433
|
+
pub fn as_packed(&self) -> Option<&Vec<f64>> {
|
|
434
|
+
match self {
|
|
435
|
+
NumArrayBacking::Packed(v) => Some(v),
|
|
436
|
+
NumArrayBacking::Boxed(_) => None,
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
#[inline]
|
|
440
|
+
pub fn as_packed_mut(&mut self) -> Option<&mut Vec<f64>> {
|
|
441
|
+
match self {
|
|
442
|
+
NumArrayBacking::Packed(v) => Some(v),
|
|
443
|
+
NumArrayBacking::Boxed(_) => None,
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/// The boxed slice, iff already deopted.
|
|
447
|
+
#[inline]
|
|
448
|
+
pub fn as_boxed(&self) -> Option<&Vec<Value>> {
|
|
449
|
+
match self {
|
|
450
|
+
NumArrayBacking::Boxed(v) => Some(v),
|
|
451
|
+
NumArrayBacking::Packed(_) => None,
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/// Upgrade to a boxed backing **in place** (idempotent) and return `&mut` to the boxed `Vec`.
|
|
455
|
+
/// Every alias sharing this cell now sees a boxed array (#506). Packed `f64`s become
|
|
456
|
+
/// `Number(n)` (NaN preserved), so a read after deopt matches what the packed form returned.
|
|
457
|
+
#[inline]
|
|
458
|
+
pub fn deopt(&mut self) -> &mut Vec<Value> {
|
|
459
|
+
if let NumArrayBacking::Packed(v) = self {
|
|
460
|
+
let boxed = v.iter().map(|n| Value::Number(*n)).collect();
|
|
461
|
+
*self = NumArrayBacking::Boxed(boxed);
|
|
462
|
+
}
|
|
463
|
+
match self {
|
|
464
|
+
NumArrayBacking::Boxed(v) => v,
|
|
465
|
+
NumArrayBacking::Packed(_) => unreachable!("just deopted"),
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
347
470
|
#[derive(Clone, Default)]
|
|
348
471
|
pub enum Value {
|
|
349
472
|
Number(f64),
|
|
350
|
-
String(
|
|
473
|
+
String(ArcStr),
|
|
351
474
|
Bool(bool),
|
|
352
475
|
#[default]
|
|
353
476
|
Null,
|
|
354
477
|
Array(VmRef<Vec<Value>>),
|
|
355
|
-
///
|
|
356
|
-
///
|
|
357
|
-
///
|
|
358
|
-
///
|
|
359
|
-
///
|
|
360
|
-
|
|
478
|
+
/// Numeric-origin array — `TISH_PACKED_ARRAYS` mode only. Starts `Packed` (a `Vec<f64>`, no
|
|
479
|
+
/// per-element boxing; enables direct `sort_unstable_by` and unboxed folds). A mutation that
|
|
480
|
+
/// introduces a **non-numeric** value ([`NumArrayBacking::deopt`]) upgrades the backing to
|
|
481
|
+
/// `Boxed` IN PLACE, so every alias sharing this `VmRef` observes the transition (#506) — the
|
|
482
|
+
/// value stays a `NumberArray` (aliases can't be rewritten to `Array`), but its backing is now
|
|
483
|
+
/// a `Vec<Value>` and it behaves exactly like a boxed array. So the tag means "numeric-origin,
|
|
484
|
+
/// possibly deopted"; access goes through the [`NumArrayBacking`] accessors, never a raw
|
|
485
|
+
/// `Vec<f64>` assumption. Created by all-numeric array literals, `new Array(n)` (zero-filled),
|
|
486
|
+
/// numeric HOF results, and typed arrays. Never created when `packed_arrays_enabled()` is
|
|
487
|
+
/// false — callers check before constructing.
|
|
488
|
+
NumberArray(VmRef<NumArrayBacking>),
|
|
361
489
|
Object(VmRef<ObjectData>),
|
|
362
490
|
/// ECMAScript-style primitive symbol (identity by `Arc`).
|
|
363
491
|
Symbol(Arc<TishSymbol>),
|
|
@@ -368,8 +496,24 @@ pub enum Value {
|
|
|
368
496
|
Promise(Arc<dyn TishPromise>),
|
|
369
497
|
/// Opaque handle to a native Rust type (e.g. Polars DataFrame).
|
|
370
498
|
Opaque(Arc<dyn TishOpaque>),
|
|
499
|
+
/// A typed native struct carried BY REFERENCE (portable / GBA native codegen). Lets a boxed
|
|
500
|
+
/// function return or pass a typed struct without serialising it to a hashmap `Object` —
|
|
501
|
+
/// property access dispatches through [`TishStruct`] (a small match, no hash lookup / alloc), and
|
|
502
|
+
/// it materialises to an `Object` only on JSON / display / `Object.keys`-family / `for..in` /
|
|
503
|
+
/// `in` / `Object.assign` / `{...s}` spread. Built via
|
|
504
|
+
/// [`Value::from_struct`]. Portable-only: the ref is a single-threaded `Rc<RefCell<dyn …>>` and
|
|
505
|
+
/// `portable` excludes `send-values`, so this never violates `Send` (which off-portable `Value`
|
|
506
|
+
/// may need). A fat pointer is 16 B, so the 24 B `Value` size guard still holds.
|
|
507
|
+
#[cfg(feature = "portable")]
|
|
508
|
+
Struct(StructRef),
|
|
371
509
|
}
|
|
372
510
|
|
|
511
|
+
/// Reference to a boxed typed struct: `Rc<RefCell<dyn TishStruct>>`. Portable-only (see [`TishStruct`]
|
|
512
|
+
/// / [`Value::Struct`]). `RefCell` is OUTSIDE the trait object so `set_prop` can borrow it mutably; the
|
|
513
|
+
/// `Rc<RefCell<Concrete>> → Rc<RefCell<dyn TishStruct>>` unsizing is a stable coercion (no nightly).
|
|
514
|
+
#[cfg(feature = "portable")]
|
|
515
|
+
pub type StructRef = alloc::rc::Rc<core::cell::RefCell<dyn TishStruct>>;
|
|
516
|
+
|
|
373
517
|
// Size guard. `Value` is 24 bytes: `String` is thin (`ArcStr`, 8B), but `Function`/`Promise`/`Opaque`
|
|
374
518
|
// are fat `Arc<dyn …>` (data+vtable, 16B) ⇒ 16B payload + discriminant = 24.
|
|
375
519
|
//
|
|
@@ -381,7 +525,7 @@ pub enum Value {
|
|
|
381
525
|
// re-attempt the box trick; only a dispatch-level change (e.g. NaN-box's branch-free tag test, not
|
|
382
526
|
// its size) could pay off. Gated to 64-bit: wasm32 (wasi) has 32-bit pointers, so size differs there.
|
|
383
527
|
#[cfg(target_pointer_width = "64")]
|
|
384
|
-
const _: () = assert!(
|
|
528
|
+
const _: () = assert!(core::mem::size_of::<Value>() == 24);
|
|
385
529
|
|
|
386
530
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
387
531
|
// #201 Stage A — representation abstraction (behavior-preserving, zero perf effect).
|
|
@@ -414,6 +558,8 @@ pub enum ValueTag {
|
|
|
414
558
|
RegExp,
|
|
415
559
|
Promise,
|
|
416
560
|
Opaque,
|
|
561
|
+
#[cfg(feature = "portable")]
|
|
562
|
+
Struct,
|
|
417
563
|
}
|
|
418
564
|
|
|
419
565
|
/// Borrowed view of a [`Value`]'s payload, mirroring the enum variants. Lets call sites
|
|
@@ -422,11 +568,11 @@ pub enum ValueTag {
|
|
|
422
568
|
/// sites. Zero-cost — each arm borrows the existing payload in place.
|
|
423
569
|
pub enum ValueRef<'a> {
|
|
424
570
|
Number(f64),
|
|
425
|
-
String(&'a
|
|
571
|
+
String(&'a ArcStr),
|
|
426
572
|
Bool(bool),
|
|
427
573
|
Null,
|
|
428
574
|
Array(&'a VmRef<Vec<Value>>),
|
|
429
|
-
NumberArray(&'a VmRef<
|
|
575
|
+
NumberArray(&'a VmRef<NumArrayBacking>),
|
|
430
576
|
Object(&'a VmRef<ObjectData>),
|
|
431
577
|
Symbol(&'a Arc<TishSymbol>),
|
|
432
578
|
Function(&'a NativeFn),
|
|
@@ -434,6 +580,8 @@ pub enum ValueRef<'a> {
|
|
|
434
580
|
RegExp(&'a VmRef<TishRegExp>),
|
|
435
581
|
Promise(&'a Arc<dyn TishPromise>),
|
|
436
582
|
Opaque(&'a Arc<dyn TishOpaque>),
|
|
583
|
+
#[cfg(feature = "portable")]
|
|
584
|
+
Struct(&'a StructRef),
|
|
437
585
|
}
|
|
438
586
|
|
|
439
587
|
impl Value {
|
|
@@ -452,7 +600,7 @@ impl Value {
|
|
|
452
600
|
/// Named constructor for a string (#201 abstraction). Accepts anything that converts
|
|
453
601
|
/// into the interned `ArcStr` (`&str`, `String`, `ArcStr`).
|
|
454
602
|
#[inline]
|
|
455
|
-
pub fn string(s: impl Into<
|
|
603
|
+
pub fn string(s: impl Into<ArcStr>) -> Self {
|
|
456
604
|
Value::String(s.into())
|
|
457
605
|
}
|
|
458
606
|
|
|
@@ -479,6 +627,8 @@ impl Value {
|
|
|
479
627
|
Value::RegExp(r) => ValueRef::RegExp(r),
|
|
480
628
|
Value::Promise(p) => ValueRef::Promise(p),
|
|
481
629
|
Value::Opaque(o) => ValueRef::Opaque(o),
|
|
630
|
+
#[cfg(feature = "portable")]
|
|
631
|
+
Value::Struct(s) => ValueRef::Struct(s),
|
|
482
632
|
}
|
|
483
633
|
}
|
|
484
634
|
|
|
@@ -499,6 +649,8 @@ impl Value {
|
|
|
499
649
|
Value::RegExp(_) => ValueTag::RegExp,
|
|
500
650
|
Value::Promise(_) => ValueTag::Promise,
|
|
501
651
|
Value::Opaque(_) => ValueTag::Opaque,
|
|
652
|
+
#[cfg(feature = "portable")]
|
|
653
|
+
Value::Struct(_) => ValueTag::Struct,
|
|
502
654
|
}
|
|
503
655
|
}
|
|
504
656
|
|
|
@@ -540,7 +692,7 @@ impl Value {
|
|
|
540
692
|
|
|
541
693
|
/// Borrow the packed-number-array payload, if this is a `NumberArray`.
|
|
542
694
|
#[inline]
|
|
543
|
-
pub fn as_number_array(&self) -> Option<&VmRef<
|
|
695
|
+
pub fn as_number_array(&self) -> Option<&VmRef<NumArrayBacking>> {
|
|
544
696
|
match self {
|
|
545
697
|
Value::NumberArray(a) => Some(a),
|
|
546
698
|
_ => None,
|
|
@@ -571,7 +723,7 @@ const PROPMAP_INLINE: usize = 8;
|
|
|
571
723
|
#[derive(Clone, Debug, Default)]
|
|
572
724
|
pub struct PropMap {
|
|
573
725
|
inline: SmallVec<[(Arc<str>, Value); PROPMAP_INLINE]>,
|
|
574
|
-
map: Option<Box<IndexMap<Arc<str>, Value,
|
|
726
|
+
map: Option<Box<IndexMap<Arc<str>, Value, RandomState>>>,
|
|
575
727
|
/// Hidden-class identity for this object's ordered key-set (JSC Structure). `EMPTY_SHAPE` (0)
|
|
576
728
|
/// for `{}`. Maintained by `insert` (new key → `shape::transition`) and reset to `DICT_SHAPE` by
|
|
577
729
|
/// `remove`. Lets the VM's inline caches compare a `u32` instead of hashing a key. INVARIANT: a
|
|
@@ -592,7 +744,7 @@ impl PropMap {
|
|
|
592
744
|
inline: SmallVec::new(),
|
|
593
745
|
map: Some(Box::new(IndexMap::with_capacity_and_hasher(
|
|
594
746
|
n,
|
|
595
|
-
|
|
747
|
+
RandomState::default(),
|
|
596
748
|
))),
|
|
597
749
|
shape: crate::shape::EMPTY_SHAPE,
|
|
598
750
|
}
|
|
@@ -693,15 +845,15 @@ impl PropMap {
|
|
|
693
845
|
}
|
|
694
846
|
if let Some(slot) = self.inline.iter_mut().find(|(k, _)| k.as_ref() == key.as_ref()) {
|
|
695
847
|
// Update existing key → value changes, layout (shape) does not.
|
|
696
|
-
return Some(
|
|
848
|
+
return Some(core::mem::replace(&mut slot.1, val));
|
|
697
849
|
}
|
|
698
850
|
// New key (inline storage) → transition the shape away from the current one.
|
|
699
851
|
self.shape = crate::shape::transition(self.shape, &key);
|
|
700
852
|
if self.inline.len() >= PROPMAP_INLINE {
|
|
701
853
|
// Promote inline storage to an insertion-ordered map (keys + their order are preserved,
|
|
702
854
|
// so the shape stays valid).
|
|
703
|
-
let mut m: IndexMap<Arc<str>, Value,
|
|
704
|
-
IndexMap::with_capacity_and_hasher(self.inline.len() + 1,
|
|
855
|
+
let mut m: IndexMap<Arc<str>, Value, RandomState> =
|
|
856
|
+
IndexMap::with_capacity_and_hasher(self.inline.len() + 1, RandomState::default());
|
|
705
857
|
for (k, v) in self.inline.drain(..) {
|
|
706
858
|
m.insert(k, v);
|
|
707
859
|
}
|
|
@@ -778,9 +930,9 @@ impl PropMap {
|
|
|
778
930
|
// If the combined worst-case size escapes inline storage, promote once up front so the
|
|
779
931
|
// per-key inserts below never reallocate or re-promote.
|
|
780
932
|
if self.map.is_none() && self.inline.len() + incoming > PROPMAP_INLINE {
|
|
781
|
-
let mut m: IndexMap<Arc<str>, Value,
|
|
933
|
+
let mut m: IndexMap<Arc<str>, Value, RandomState> = IndexMap::with_capacity_and_hasher(
|
|
782
934
|
self.inline.len() + incoming,
|
|
783
|
-
|
|
935
|
+
RandomState::default(),
|
|
784
936
|
);
|
|
785
937
|
for (k, v) in self.inline.drain(..) {
|
|
786
938
|
m.insert(k, v);
|
|
@@ -826,7 +978,7 @@ impl IntoIterator for PropMap {
|
|
|
826
978
|
|
|
827
979
|
/// Zero-allocation borrowing iterator over [`PropMap`] entries (insertion order).
|
|
828
980
|
pub enum PropMapIter<'a> {
|
|
829
|
-
Inline(
|
|
981
|
+
Inline(core::slice::Iter<'a, (Arc<str>, Value)>),
|
|
830
982
|
Map(indexmap::map::Iter<'a, Arc<str>, Value>),
|
|
831
983
|
}
|
|
832
984
|
impl<'a> Iterator for PropMapIter<'a> {
|
|
@@ -842,7 +994,7 @@ impl<'a> Iterator for PropMapIter<'a> {
|
|
|
842
994
|
|
|
843
995
|
/// Zero-allocation key iterator over [`PropMap`] (insertion order).
|
|
844
996
|
pub enum PropMapKeys<'a> {
|
|
845
|
-
Inline(
|
|
997
|
+
Inline(core::slice::Iter<'a, (Arc<str>, Value)>),
|
|
846
998
|
Map(indexmap::map::Keys<'a, Arc<str>, Value>),
|
|
847
999
|
}
|
|
848
1000
|
impl<'a> Iterator for PropMapKeys<'a> {
|
|
@@ -858,7 +1010,7 @@ impl<'a> Iterator for PropMapKeys<'a> {
|
|
|
858
1010
|
|
|
859
1011
|
/// Zero-allocation value iterator over [`PropMap`] (insertion order).
|
|
860
1012
|
pub enum PropMapValues<'a> {
|
|
861
|
-
Inline(
|
|
1013
|
+
Inline(core::slice::Iter<'a, (Arc<str>, Value)>),
|
|
862
1014
|
Map(indexmap::map::Values<'a, Arc<str>, Value>),
|
|
863
1015
|
}
|
|
864
1016
|
impl<'a> Iterator for PropMapValues<'a> {
|
|
@@ -894,6 +1046,10 @@ impl Iterator for PropMapIntoIter {
|
|
|
894
1046
|
pub struct ObjectData {
|
|
895
1047
|
pub strings: PropMap,
|
|
896
1048
|
pub symbols: Option<AHashMap<u64, Value>>,
|
|
1049
|
+
/// `Object.freeze` marker (#437). When true, property writes must throw a catchable TypeError
|
|
1050
|
+
/// instead of mutating — enforced at every write chokepoint (interp assign, vm `set_member`,
|
|
1051
|
+
/// native `set_prop`). Reference-shared like the rest of the object; a fresh object is not frozen.
|
|
1052
|
+
pub frozen: bool,
|
|
897
1053
|
}
|
|
898
1054
|
|
|
899
1055
|
impl ObjectData {
|
|
@@ -902,9 +1058,16 @@ impl ObjectData {
|
|
|
902
1058
|
Self {
|
|
903
1059
|
strings: strings.into_iter().collect(),
|
|
904
1060
|
symbols: None,
|
|
1061
|
+
frozen: false,
|
|
905
1062
|
}
|
|
906
1063
|
}
|
|
907
1064
|
|
|
1065
|
+
/// True if `Object.freeze` has been applied to this object.
|
|
1066
|
+
#[inline]
|
|
1067
|
+
pub fn is_frozen(&self) -> bool {
|
|
1068
|
+
self.frozen
|
|
1069
|
+
}
|
|
1070
|
+
|
|
908
1071
|
#[inline]
|
|
909
1072
|
pub fn len_entries(&self) -> usize {
|
|
910
1073
|
self.strings.len() + self.symbols.as_ref().map(|s| s.len()).unwrap_or(0)
|
|
@@ -1084,15 +1247,15 @@ pub fn value_call(callee: &Value, args: &[Value]) -> Value {
|
|
|
1084
1247
|
return value_call(&inner, args);
|
|
1085
1248
|
}
|
|
1086
1249
|
crate::set_pending_throw(crate::not_a_function_error(format!(
|
|
1087
|
-
"
|
|
1088
|
-
callee.
|
|
1250
|
+
"Call of non-function: {}",
|
|
1251
|
+
callee.type_name()
|
|
1089
1252
|
)));
|
|
1090
1253
|
Value::Null
|
|
1091
1254
|
}
|
|
1092
1255
|
_ => {
|
|
1093
1256
|
crate::set_pending_throw(crate::not_a_function_error(format!(
|
|
1094
|
-
"
|
|
1095
|
-
callee.
|
|
1257
|
+
"Call of non-function: {}",
|
|
1258
|
+
callee.type_name()
|
|
1096
1259
|
)));
|
|
1097
1260
|
Value::Null
|
|
1098
1261
|
}
|
|
@@ -1118,11 +1281,15 @@ pub fn merge_object_data(left: &VmRef<ObjectData>, right: &VmRef<ObjectData>) ->
|
|
|
1118
1281
|
None => symbols = Some(rs.clone()),
|
|
1119
1282
|
}
|
|
1120
1283
|
}
|
|
1121
|
-
ObjectData {
|
|
1284
|
+
ObjectData {
|
|
1285
|
+
strings,
|
|
1286
|
+
symbols,
|
|
1287
|
+
frozen: false,
|
|
1288
|
+
}
|
|
1122
1289
|
}
|
|
1123
1290
|
|
|
1124
|
-
impl
|
|
1125
|
-
fn fmt(&self, f: &mut
|
|
1291
|
+
impl core::fmt::Debug for Value {
|
|
1292
|
+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
1126
1293
|
match self {
|
|
1127
1294
|
Value::Number(n) => write!(f, "Number({})", n),
|
|
1128
1295
|
Value::String(s) => write!(f, "String({:?})", s.as_str()),
|
|
@@ -1142,6 +1309,8 @@ impl std::fmt::Debug for Value {
|
|
|
1142
1309
|
),
|
|
1143
1310
|
Value::Promise(_) => write!(f, "Promise"),
|
|
1144
1311
|
Value::Opaque(o) => write!(f, "{}(opaque)", o.type_name()),
|
|
1312
|
+
#[cfg(feature = "portable")]
|
|
1313
|
+
Value::Struct(s) => write!(f, "Struct({})", s.borrow().tish_type_name()),
|
|
1145
1314
|
}
|
|
1146
1315
|
}
|
|
1147
1316
|
}
|
|
@@ -1242,7 +1411,7 @@ pub fn js_number_to_string_into(out: &mut String, value: f64) {
|
|
|
1242
1411
|
end -= 1;
|
|
1243
1412
|
}
|
|
1244
1413
|
let trail = (n - end) as i32;
|
|
1245
|
-
let digits =
|
|
1414
|
+
let digits = core::str::from_utf8(&digits_buf[lead..end]).expect("ascii digits");
|
|
1246
1415
|
let k = digits.len() as i32; // significant digit count (≤ 17 for an f64)
|
|
1247
1416
|
let point = k + exp - frac_len + trail;
|
|
1248
1417
|
|
|
@@ -1318,8 +1487,12 @@ impl Value {
|
|
|
1318
1487
|
Value::NumberArray(arr) => {
|
|
1319
1488
|
let inner: Vec<String> = arr
|
|
1320
1489
|
.borrow()
|
|
1490
|
+
.to_values()
|
|
1321
1491
|
.iter()
|
|
1322
|
-
.map(
|
|
1492
|
+
.map(|v| match v {
|
|
1493
|
+
Value::Number(n) if n.is_nan() => "null".to_string(),
|
|
1494
|
+
other => other.to_display_string(),
|
|
1495
|
+
})
|
|
1323
1496
|
.collect();
|
|
1324
1497
|
format!("[{}]", inner.join(", "))
|
|
1325
1498
|
}
|
|
@@ -1348,6 +1521,9 @@ impl Value {
|
|
|
1348
1521
|
Value::Function(_) => "[Function]".to_string(),
|
|
1349
1522
|
Value::Promise(_) => "[object Promise]".to_string(),
|
|
1350
1523
|
Value::Opaque(o) => format!("[object {}]", o.type_name()),
|
|
1524
|
+
// Display a boxed struct like the object it is (materialise once, then reuse the Object path).
|
|
1525
|
+
#[cfg(feature = "portable")]
|
|
1526
|
+
Value::Struct(s) => s.borrow().tish_to_object().to_display_string_guarded(ancestors),
|
|
1351
1527
|
#[cfg(feature = "regex")]
|
|
1352
1528
|
Value::RegExp(re) => {
|
|
1353
1529
|
let re = re.borrow();
|
|
@@ -1393,11 +1569,14 @@ impl Value {
|
|
|
1393
1569
|
}
|
|
1394
1570
|
Value::NumberArray(arr) => arr
|
|
1395
1571
|
.borrow()
|
|
1572
|
+
.to_values()
|
|
1396
1573
|
.iter()
|
|
1397
|
-
.map(|
|
|
1574
|
+
.map(|v| v.to_js_string())
|
|
1398
1575
|
.collect::<Vec<_>>()
|
|
1399
1576
|
.join(","),
|
|
1400
1577
|
Value::Object(_) => "[object Object]".to_string(),
|
|
1578
|
+
#[cfg(feature = "portable")]
|
|
1579
|
+
Value::Struct(_) => "[object Object]".to_string(),
|
|
1401
1580
|
// ECMAScript ToString of a number (drops `-0`'s sign), distinct from the inspect form
|
|
1402
1581
|
// that `to_display_string` would give for `-0`. (#247)
|
|
1403
1582
|
Value::Number(n) => js_number_to_string(*n),
|
|
@@ -1436,12 +1615,16 @@ impl Value {
|
|
|
1436
1615
|
#[cfg(feature = "send-values")]
|
|
1437
1616
|
(Value::Function(a), Value::Function(b)) => Arc::ptr_eq(a, b),
|
|
1438
1617
|
#[cfg(not(feature = "send-values"))]
|
|
1439
|
-
(Value::Function(a), Value::Function(b)) =>
|
|
1618
|
+
(Value::Function(a), Value::Function(b)) => alloc::rc::Rc::ptr_eq(a, b),
|
|
1440
1619
|
#[cfg(feature = "regex")]
|
|
1441
1620
|
(Value::RegExp(a), Value::RegExp(b)) => VmRef::ptr_eq(a, b),
|
|
1442
1621
|
(Value::Promise(a), Value::Promise(b)) => Arc::ptr_eq(a, b),
|
|
1443
1622
|
(Value::Opaque(a), Value::Opaque(b)) => Arc::ptr_eq(a, b),
|
|
1444
1623
|
(Value::Symbol(a), Value::Symbol(b)) => Arc::ptr_eq(a, b),
|
|
1624
|
+
// Struct-Value identity is `Rc` pointer equality, exactly like `Object` — two distinct
|
|
1625
|
+
// boxings compare unequal, but a struct aliased through one `Value` is `=== ` itself.
|
|
1626
|
+
#[cfg(feature = "portable")]
|
|
1627
|
+
(Value::Struct(a), Value::Struct(b)) => alloc::rc::Rc::ptr_eq(a, b),
|
|
1445
1628
|
_ => false,
|
|
1446
1629
|
}
|
|
1447
1630
|
}
|
|
@@ -1465,7 +1648,7 @@ impl Value {
|
|
|
1465
1648
|
where
|
|
1466
1649
|
F: Fn(&[Value]) -> Value + 'static,
|
|
1467
1650
|
{
|
|
1468
|
-
Value::Function(
|
|
1651
|
+
Value::Function(alloc::rc::Rc::new(FnCallable(f)))
|
|
1469
1652
|
}
|
|
1470
1653
|
|
|
1471
1654
|
/// Create a new array Value from a Vec.
|
|
@@ -1490,9 +1673,67 @@ impl Value {
|
|
|
1490
1673
|
Value::Object(VmRef::new(ObjectData {
|
|
1491
1674
|
strings,
|
|
1492
1675
|
symbols: None,
|
|
1676
|
+
frozen: false,
|
|
1493
1677
|
}))
|
|
1494
1678
|
}
|
|
1495
1679
|
|
|
1680
|
+
/// Box a typed native struct (a generated `TishStruct_*`). On portable (GBA) it is kept BY
|
|
1681
|
+
/// REFERENCE as a cheap [`Value::Struct`] — one `Rc` allocation, no per-field string keys / hashmap;
|
|
1682
|
+
/// property access later dispatches through [`TishStruct`]. On every other backend it serialises to
|
|
1683
|
+
/// an ordered `Value::Object` (via [`TishStruct::tish_to_object`]) — byte-identical to the historical
|
|
1684
|
+
/// `object_from_pairs` boundary, so off-GBA behaviour is unchanged. Codegen's struct-to-`Value`
|
|
1685
|
+
/// boxing emits a single `Value::from_struct(..)` call for all native targets.
|
|
1686
|
+
#[cfg(feature = "portable")]
|
|
1687
|
+
#[inline]
|
|
1688
|
+
pub fn from_struct<T: TishStruct + 'static>(v: T) -> Value {
|
|
1689
|
+
Value::Struct(alloc::rc::Rc::new(core::cell::RefCell::new(v)))
|
|
1690
|
+
}
|
|
1691
|
+
/// See the portable variant above — off-GBA this is exactly the old `object_from_pairs` boundary.
|
|
1692
|
+
#[cfg(not(feature = "portable"))]
|
|
1693
|
+
#[inline]
|
|
1694
|
+
pub fn from_struct<T: TishStruct>(v: T) -> Value {
|
|
1695
|
+
v.tish_to_object()
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
/// Box a typed native struct BY SHARED REFERENCE — the boxing path codegen actually emits, so the
|
|
1699
|
+
/// whole-struct clone is paid ONLY where it's genuinely needed. On portable (GBA) the struct must
|
|
1700
|
+
/// be OWNED by the `Rc`, so it clones (identical to `from_struct((v).clone())`). Off-GBA it
|
|
1701
|
+
/// serialises the borrow directly via [`TishStruct::tish_to_object`] with NO whole-struct clone —
|
|
1702
|
+
/// byte-identical to the historical `object_from_pairs` boundary that read fields straight off the
|
|
1703
|
+
/// place (#565: the old `from_struct((v).clone())` cloned the entire struct just to serialise it).
|
|
1704
|
+
#[cfg(feature = "portable")]
|
|
1705
|
+
#[inline]
|
|
1706
|
+
pub fn from_struct_ref<T: TishStruct + Clone + 'static>(v: &T) -> Value {
|
|
1707
|
+
Value::Struct(alloc::rc::Rc::new(core::cell::RefCell::new(v.clone())))
|
|
1708
|
+
}
|
|
1709
|
+
/// See the portable variant — off-GBA this serialises the borrow with no whole-struct clone.
|
|
1710
|
+
#[cfg(not(feature = "portable"))]
|
|
1711
|
+
#[inline]
|
|
1712
|
+
pub fn from_struct_ref<T: TishStruct>(v: &T) -> Value {
|
|
1713
|
+
v.tish_to_object()
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
/// If this value is a by-reference boxed struct of concrete type `T` (a `Value::Struct`), return a
|
|
1717
|
+
/// CLONE of it — the native fast path for `let x: T = <boxed struct return>`, so subsequent field
|
|
1718
|
+
/// reads are plain Rust loads instead of `get_prop`. Portable only: off-GBA there is no `Value::Struct`
|
|
1719
|
+
/// variant, so this is always `None` and codegen's `from_value_expr` falls back to the field-by-field
|
|
1720
|
+
/// `get_prop` rebuild (which also handles a genuine `Value::Object`). Returns `None` for any non-struct
|
|
1721
|
+
/// value or a struct of a different concrete type.
|
|
1722
|
+
#[cfg(feature = "portable")]
|
|
1723
|
+
#[inline]
|
|
1724
|
+
pub fn downcast_struct<T: TishStruct + Clone + 'static>(&self) -> Option<T> {
|
|
1725
|
+
match self {
|
|
1726
|
+
Value::Struct(s) => s.borrow().as_any().downcast_ref::<T>().cloned(),
|
|
1727
|
+
_ => None,
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
/// See the portable variant — off-GBA there is no struct variant, so always `None`.
|
|
1731
|
+
#[cfg(not(feature = "portable"))]
|
|
1732
|
+
#[inline]
|
|
1733
|
+
pub fn downcast_struct<T: TishStruct + Clone + 'static>(&self) -> Option<T> {
|
|
1734
|
+
None
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1496
1737
|
/// Create an empty array Value.
|
|
1497
1738
|
pub fn empty_array() -> Self {
|
|
1498
1739
|
Value::Array(VmRef::new(Vec::new()))
|
|
@@ -1509,6 +1750,7 @@ impl Value {
|
|
|
1509
1750
|
/// process starts (as the CI sweep does); mid-process toggling is not observed by design.
|
|
1510
1751
|
/// The flag is intentionally backwards from the slot/JIT flags (those were default-on) to
|
|
1511
1752
|
/// keep the default binary behaviour byte-identical while we validate coverage.
|
|
1753
|
+
#[cfg(not(feature = "portable"))]
|
|
1512
1754
|
#[inline]
|
|
1513
1755
|
pub fn packed_arrays_enabled() -> bool {
|
|
1514
1756
|
static PACKED_ARRAYS: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
|
|
@@ -1517,10 +1759,18 @@ impl Value {
|
|
|
1517
1759
|
})
|
|
1518
1760
|
}
|
|
1519
1761
|
|
|
1762
|
+
/// Packed f64 arrays are off under `portable` (no env to opt in; keeps the
|
|
1763
|
+
/// generic array path, which the GBA runtime already exercises).
|
|
1764
|
+
#[cfg(feature = "portable")]
|
|
1765
|
+
#[inline]
|
|
1766
|
+
pub fn packed_arrays_enabled() -> bool {
|
|
1767
|
+
false
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1520
1770
|
/// Wrap a `Vec<f64>` as a `Value::NumberArray`. Only call when `packed_arrays_enabled()`.
|
|
1521
1771
|
#[inline]
|
|
1522
1772
|
pub fn number_array(items: Vec<f64>) -> Self {
|
|
1523
|
-
Value::NumberArray(VmRef::new(items))
|
|
1773
|
+
Value::NumberArray(VmRef::new(NumArrayBacking::Packed(items)))
|
|
1524
1774
|
}
|
|
1525
1775
|
|
|
1526
1776
|
/// Materialize a `Value::NumberArray` into a boxed `Value::Array`.
|
|
@@ -1529,9 +1779,8 @@ impl Value {
|
|
|
1529
1779
|
/// converts once and continues on the generic path. The original `NumberArray` VmRef
|
|
1530
1780
|
/// is consumed; callers replace the `Value` in whatever container held it.
|
|
1531
1781
|
#[inline]
|
|
1532
|
-
pub fn materialize_number_array(arr: &VmRef<
|
|
1533
|
-
|
|
1534
|
-
Value::Array(VmRef::new(nums.iter().map(|&n| Value::Number(n)).collect()))
|
|
1782
|
+
pub fn materialize_number_array(arr: &VmRef<NumArrayBacking>) -> Value {
|
|
1783
|
+
Value::Array(VmRef::new(arr.borrow().to_values()))
|
|
1535
1784
|
}
|
|
1536
1785
|
|
|
1537
1786
|
/// If `self` is a `NumberArray`, materialise and return `Value::Array`; otherwise
|
|
@@ -1572,6 +1821,8 @@ impl Value {
|
|
|
1572
1821
|
Value::Promise(_) => "object",
|
|
1573
1822
|
Value::Opaque(o) => o.type_name(),
|
|
1574
1823
|
Value::Symbol(_) => "symbol",
|
|
1824
|
+
#[cfg(feature = "portable")]
|
|
1825
|
+
Value::Struct(_) => "object",
|
|
1575
1826
|
}
|
|
1576
1827
|
}
|
|
1577
1828
|
|