@tishlang/tish-lsp 2.39.0 → 2.43.8
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-lsp +0 -0
- package/crates/tish/src/cli_help.rs +28 -0
- package/crates/tish/src/main.rs +41 -5
- 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 +38 -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 +806 -46
- 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 +148 -10
- package/crates/tish_compile/src/schemes.rs +278 -0
- package/crates/tish_compile/src/types.rs +141 -2
- 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_gba_numerics_gated.rs +58 -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 +19 -1
- package/crates/tish_core/src/json.rs +34 -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 +182 -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_lsp/Cargo.toml +3 -1
- 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 +54 -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 +620 -0
- package/crates/tish_vm/src/jit.rs +1446 -164
- package/crates/tish_vm/src/vm.rs +535 -173
- package/justfile +9 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-lsp +0 -0
- package/platform/darwin-x64/tish-lsp +0 -0
- package/platform/linux-arm64/tish-lsp +0 -0
- package/platform/linux-x64/tish-lsp +0 -0
- package/platform/win32-x64/tish-lsp.exe +0 -0
|
@@ -293,6 +293,16 @@ impl Evaluator {
|
|
|
293
293
|
math.insert("log10".into(), Value::Native(natives::math_log10));
|
|
294
294
|
math.insert("PI".into(), Value::Number(std::f64::consts::PI));
|
|
295
295
|
math.insert("E".into(), Value::Number(std::f64::consts::E));
|
|
296
|
+
// #539: the remaining ECMAScript Math value constants (were missing → `null`).
|
|
297
|
+
math.insert("LN2".into(), Value::Number(std::f64::consts::LN_2));
|
|
298
|
+
math.insert("LN10".into(), Value::Number(std::f64::consts::LN_10));
|
|
299
|
+
math.insert("LOG2E".into(), Value::Number(std::f64::consts::LOG2_E));
|
|
300
|
+
math.insert("LOG10E".into(), Value::Number(std::f64::consts::LOG10_E));
|
|
301
|
+
math.insert("SQRT2".into(), Value::Number(std::f64::consts::SQRT_2));
|
|
302
|
+
math.insert(
|
|
303
|
+
"SQRT1_2".into(),
|
|
304
|
+
Value::Number(std::f64::consts::FRAC_1_SQRT_2),
|
|
305
|
+
);
|
|
296
306
|
s.set(
|
|
297
307
|
"Math".into(),
|
|
298
308
|
Value::object(math),
|
|
@@ -336,6 +346,8 @@ impl Evaluator {
|
|
|
336
346
|
|
|
337
347
|
let mut object = PropMap::with_capacity(5);
|
|
338
348
|
object.insert("keys".into(), Value::Native(Self::object_keys));
|
|
349
|
+
object.insert("freeze".into(), Value::Native(Self::object_freeze));
|
|
350
|
+
object.insert("isFrozen".into(), Value::Native(Self::object_is_frozen));
|
|
339
351
|
object.insert("values".into(), Value::Native(Self::object_values));
|
|
340
352
|
object.insert("entries".into(), Value::Native(Self::object_entries));
|
|
341
353
|
object.insert("assign".into(), Value::Native(Self::object_assign));
|
|
@@ -1190,6 +1202,10 @@ impl Evaluator {
|
|
|
1190
1202
|
from
|
|
1191
1203
|
)));
|
|
1192
1204
|
}
|
|
1205
|
+
let ctx = tishlang_compile::resolve_context();
|
|
1206
|
+
if let Some(path) = tishlang_compile::resolve_with_platform(from, dir, ctx) {
|
|
1207
|
+
return Ok(path);
|
|
1208
|
+
}
|
|
1193
1209
|
let base = dir.join(from);
|
|
1194
1210
|
let path = if base.extension().is_none() {
|
|
1195
1211
|
let with_ext = base.with_extension("tish");
|
|
@@ -3366,6 +3382,17 @@ impl Evaluator {
|
|
|
3366
3382
|
let val = self.eval_expr(value)?;
|
|
3367
3383
|
match obj_val {
|
|
3368
3384
|
Value::Object(map) => {
|
|
3385
|
+
if map.borrow().frozen {
|
|
3386
|
+
return Err(EvalError::Throw(
|
|
3387
|
+
crate::natives::type_error_construct(&[Value::String(
|
|
3388
|
+
format!(
|
|
3389
|
+
"Cannot assign to read only property '{prop}' of a frozen object"
|
|
3390
|
+
)
|
|
3391
|
+
.into(),
|
|
3392
|
+
)])
|
|
3393
|
+
.unwrap_or(Value::Null),
|
|
3394
|
+
));
|
|
3395
|
+
}
|
|
3369
3396
|
map.borrow_mut()
|
|
3370
3397
|
.strings
|
|
3371
3398
|
.insert(Arc::clone(prop), val.clone());
|
|
@@ -3417,7 +3444,18 @@ impl Evaluator {
|
|
|
3417
3444
|
arr_mut[idx] = val.clone();
|
|
3418
3445
|
Ok(val)
|
|
3419
3446
|
}
|
|
3420
|
-
Value::Object(
|
|
3447
|
+
Value::Object(ref map) => {
|
|
3448
|
+
if map.borrow().frozen {
|
|
3449
|
+
return Err(EvalError::Throw(
|
|
3450
|
+
crate::natives::type_error_construct(&[Value::String(
|
|
3451
|
+
format!(
|
|
3452
|
+
"Cannot assign to read only property '{idx_val}' of a frozen object"
|
|
3453
|
+
)
|
|
3454
|
+
.into(),
|
|
3455
|
+
)])
|
|
3456
|
+
.unwrap_or(Value::Null),
|
|
3457
|
+
));
|
|
3458
|
+
}
|
|
3421
3459
|
eval_object_set(&obj_val, &idx_val, val.clone())
|
|
3422
3460
|
.map_err(EvalError::Error)?;
|
|
3423
3461
|
Ok(val)
|
|
@@ -3912,7 +3950,23 @@ impl Evaluator {
|
|
|
3912
3950
|
if let Some(call) = o.borrow().strings.get("__call").cloned() {
|
|
3913
3951
|
return self.call_func(&call, args);
|
|
3914
3952
|
}
|
|
3915
|
-
Err(EvalError::
|
|
3953
|
+
Err(EvalError::Throw(
|
|
3954
|
+
crate::natives::type_error_construct(&[Value::String(
|
|
3955
|
+
format!(
|
|
3956
|
+
"Call of non-function: {}",
|
|
3957
|
+
match f {
|
|
3958
|
+
Value::Number(_) => "number",
|
|
3959
|
+
Value::String(_) => "string",
|
|
3960
|
+
Value::Bool(_) => "boolean",
|
|
3961
|
+
Value::Null => "null",
|
|
3962
|
+
Value::Symbol(_) => "symbol",
|
|
3963
|
+
_ => "object",
|
|
3964
|
+
}
|
|
3965
|
+
)
|
|
3966
|
+
.into(),
|
|
3967
|
+
)])
|
|
3968
|
+
.unwrap_or(Value::Null),
|
|
3969
|
+
))
|
|
3916
3970
|
}
|
|
3917
3971
|
Value::Native(native_fn) => {
|
|
3918
3972
|
// #122: Node fs callback form — `readFile(path, (err, data) => …)`. The self-less
|
|
@@ -4154,7 +4208,23 @@ impl Evaluator {
|
|
|
4154
4208
|
}
|
|
4155
4209
|
}
|
|
4156
4210
|
}
|
|
4157
|
-
_ => Err(EvalError::
|
|
4211
|
+
_ => Err(EvalError::Throw(
|
|
4212
|
+
crate::natives::type_error_construct(&[Value::String(
|
|
4213
|
+
format!(
|
|
4214
|
+
"Call of non-function: {}",
|
|
4215
|
+
match f {
|
|
4216
|
+
Value::Number(_) => "number",
|
|
4217
|
+
Value::String(_) => "string",
|
|
4218
|
+
Value::Bool(_) => "boolean",
|
|
4219
|
+
Value::Null => "null",
|
|
4220
|
+
Value::Symbol(_) => "symbol",
|
|
4221
|
+
_ => "object",
|
|
4222
|
+
}
|
|
4223
|
+
)
|
|
4224
|
+
.into(),
|
|
4225
|
+
)])
|
|
4226
|
+
.unwrap_or(Value::Null),
|
|
4227
|
+
)),
|
|
4158
4228
|
}
|
|
4159
4229
|
}
|
|
4160
4230
|
|
|
@@ -4852,6 +4922,23 @@ impl Evaluator {
|
|
|
4852
4922
|
}
|
|
4853
4923
|
}
|
|
4854
4924
|
|
|
4925
|
+
/// Object.freeze(obj) — mark frozen so writes throw a catchable TypeError (#437); returns obj.
|
|
4926
|
+
fn object_freeze(args: &[Value]) -> Result<Value, String> {
|
|
4927
|
+
if let Some(Value::Object(obj)) = args.first() {
|
|
4928
|
+
obj.borrow_mut().frozen = true;
|
|
4929
|
+
}
|
|
4930
|
+
Ok(args.first().cloned().unwrap_or(Value::Null))
|
|
4931
|
+
}
|
|
4932
|
+
|
|
4933
|
+
/// Object.isFrozen(obj) — true if frozen; primitives are frozen, arrays are not (this slice).
|
|
4934
|
+
fn object_is_frozen(args: &[Value]) -> Result<Value, String> {
|
|
4935
|
+
Ok(match args.first() {
|
|
4936
|
+
Some(Value::Object(obj)) => Value::Bool(obj.borrow().frozen),
|
|
4937
|
+
Some(Value::Array(_)) => Value::Bool(false),
|
|
4938
|
+
_ => Value::Bool(true),
|
|
4939
|
+
})
|
|
4940
|
+
}
|
|
4941
|
+
|
|
4855
4942
|
fn object_keys(args: &[Value]) -> Result<Value, String> {
|
|
4856
4943
|
match args.first() {
|
|
4857
4944
|
Some(Value::Object(obj)) => {
|
|
@@ -24,6 +24,8 @@ pub type PropMap = indexmap::IndexMap<Arc<str>, Value>;
|
|
|
24
24
|
pub struct EvalObjectData {
|
|
25
25
|
pub strings: PropMap,
|
|
26
26
|
pub symbols: Option<AHashMap<u64, Value>>,
|
|
27
|
+
/// `Object.freeze` marker (#437) — writes to a frozen object throw a catchable TypeError.
|
|
28
|
+
pub frozen: bool,
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
impl EvalObjectData {
|
|
@@ -31,8 +33,14 @@ impl EvalObjectData {
|
|
|
31
33
|
Self {
|
|
32
34
|
strings,
|
|
33
35
|
symbols: None,
|
|
36
|
+
frozen: false,
|
|
34
37
|
}
|
|
35
38
|
}
|
|
39
|
+
|
|
40
|
+
#[inline]
|
|
41
|
+
pub fn is_frozen(&self) -> bool {
|
|
42
|
+
self.frozen
|
|
43
|
+
}
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
pub fn eval_object_get(obj: &Value, key: &Value) -> Option<Value> {
|
|
@@ -1,16 +1,77 @@
|
|
|
1
1
|
//! Conversion between tishlang_eval::Value and tishlang_core::Value for opaque method calls.
|
|
2
2
|
|
|
3
3
|
use std::cell::RefCell;
|
|
4
|
-
use std::rc::Rc;
|
|
4
|
+
use std::rc::{Rc, Weak};
|
|
5
5
|
use std::sync::Arc;
|
|
6
6
|
|
|
7
7
|
#[cfg(feature = "regex")]
|
|
8
8
|
use tishlang_core::TishRegExp;
|
|
9
|
-
use ahash::AHashMap;
|
|
9
|
+
use ahash::{AHashMap, AHashSet};
|
|
10
10
|
use tishlang_core::{ObjectData, Value as CoreValue, VmRef};
|
|
11
11
|
|
|
12
12
|
use crate::value::{EvalObjectData, PropMap, Value};
|
|
13
13
|
|
|
14
|
+
/// #466 — identity-stable bridging for reference values (objects, arrays).
|
|
15
|
+
///
|
|
16
|
+
/// Core `Map`/`Set` key equality is SameValueZero (`VmRef::ptr_eq` for objects/arrays), so a fresh
|
|
17
|
+
/// `VmRef` per `eval_to_core` call made `m.get(k)`/`s.has(a)` fail for ANY object/array key in the
|
|
18
|
+
/// interpreter: the stored key and the lookup probe were different core allocations. This cache
|
|
19
|
+
/// gives every eval `Rc` a single stable core **twin**: repeat bridges return the same `VmRef`
|
|
20
|
+
/// (identity holds), with contents refreshed from the eval side on each bridge (preserving the
|
|
21
|
+
/// existing copy-at-the-boundary semantics — and matching vm/node more closely on
|
|
22
|
+
/// mutate-after-insert, since the stored key's view updates with its object).
|
|
23
|
+
///
|
|
24
|
+
/// The reverse maps let `core_to_eval` return the ORIGINAL eval object when a forward-bridged twin
|
|
25
|
+
/// flows back out (`m.get(k) === v`, `[...m.keys()][0] === k` — identity across the round trip,
|
|
26
|
+
/// as on vm/native/node).
|
|
27
|
+
///
|
|
28
|
+
/// Soundness against address reuse (the #250 lesson): forward entries pair the twin with a `Weak`
|
|
29
|
+
/// to the eval `Rc` and every hit verifies `upgrade() + Rc::ptr_eq`; a dead/differing weak means
|
|
30
|
+
/// the address was reused and the entry is replaced. Reverse hits re-verify through the forward
|
|
31
|
+
/// entry (`VmRef::ptr_eq`), and a live forward entry holds the twin strongly, so a live reverse
|
|
32
|
+
/// key's address cannot have been recycled. `in_flight` makes conversion cycle-safe: a
|
|
33
|
+
/// self/mutually-referential object hit mid-refresh returns its twin as-is instead of recursing
|
|
34
|
+
/// forever (the enclosing refresh completes the contents).
|
|
35
|
+
struct BridgeCache {
|
|
36
|
+
obj_fwd: AHashMap<usize, (Weak<RefCell<EvalObjectData>>, VmRef<ObjectData>)>,
|
|
37
|
+
arr_fwd: AHashMap<usize, (Weak<RefCell<Vec<Value>>>, VmRef<Vec<CoreValue>>)>,
|
|
38
|
+
obj_rev: AHashMap<usize, Weak<RefCell<EvalObjectData>>>,
|
|
39
|
+
arr_rev: AHashMap<usize, Weak<RefCell<Vec<Value>>>>,
|
|
40
|
+
/// Eval addresses currently being refreshed (cycle guard).
|
|
41
|
+
in_flight: AHashSet<usize>,
|
|
42
|
+
/// Amortized cleanup threshold: sweep dead entries when the cache outgrows it.
|
|
43
|
+
high_water: usize,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl BridgeCache {
|
|
47
|
+
fn new() -> Self {
|
|
48
|
+
Self {
|
|
49
|
+
obj_fwd: AHashMap::default(),
|
|
50
|
+
arr_fwd: AHashMap::default(),
|
|
51
|
+
obj_rev: AHashMap::default(),
|
|
52
|
+
arr_rev: AHashMap::default(),
|
|
53
|
+
in_flight: AHashSet::default(),
|
|
54
|
+
high_water: 256,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Drop entries whose eval side has died. Called on the insert path only; amortized O(1).
|
|
59
|
+
fn maybe_sweep(&mut self) {
|
|
60
|
+
if self.obj_fwd.len() + self.arr_fwd.len() <= self.high_water {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
self.obj_fwd.retain(|_, (w, _)| w.upgrade().is_some());
|
|
64
|
+
self.arr_fwd.retain(|_, (w, _)| w.upgrade().is_some());
|
|
65
|
+
self.obj_rev.retain(|_, w| w.upgrade().is_some());
|
|
66
|
+
self.arr_rev.retain(|_, w| w.upgrade().is_some());
|
|
67
|
+
self.high_water = ((self.obj_fwd.len() + self.arr_fwd.len()) * 2).max(256);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
thread_local! {
|
|
72
|
+
static BRIDGE: RefCell<BridgeCache> = RefCell::new(BridgeCache::new());
|
|
73
|
+
}
|
|
74
|
+
|
|
14
75
|
/// Convert interpreter Value to core Value. Fails for interpreter-only variants.
|
|
15
76
|
pub fn eval_to_core(v: &Value) -> Result<CoreValue, String> {
|
|
16
77
|
match v {
|
|
@@ -19,28 +80,110 @@ pub fn eval_to_core(v: &Value) -> Result<CoreValue, String> {
|
|
|
19
80
|
Value::Bool(b) => Ok(CoreValue::Bool(*b)),
|
|
20
81
|
Value::Null => Ok(CoreValue::Null),
|
|
21
82
|
Value::Array(arr) => {
|
|
22
|
-
let
|
|
23
|
-
|
|
24
|
-
|
|
83
|
+
let addr = Rc::as_ptr(arr) as usize;
|
|
84
|
+
// Cycle guard: this array is already being refreshed higher up this same
|
|
85
|
+
// conversion — hand back its twin as-is; the enclosing refresh fills it.
|
|
86
|
+
if let Some(twin) = BRIDGE.with(|c| {
|
|
87
|
+
let c = c.borrow();
|
|
88
|
+
if c.in_flight.contains(&addr) {
|
|
89
|
+
c.arr_fwd
|
|
90
|
+
.get(&addr)
|
|
91
|
+
.filter(|(w, _)| w.upgrade().is_some_and(|up| Rc::ptr_eq(&up, arr)))
|
|
92
|
+
.map(|(_, t)| t.clone())
|
|
93
|
+
} else {
|
|
94
|
+
None
|
|
95
|
+
}
|
|
96
|
+
}) {
|
|
97
|
+
return Ok(CoreValue::Array(twin));
|
|
25
98
|
}
|
|
26
|
-
|
|
99
|
+
// Resolve (or mint) the stable core twin for this eval Rc, then refresh contents.
|
|
100
|
+
let twin = BRIDGE.with(|c| {
|
|
101
|
+
let mut c = c.borrow_mut();
|
|
102
|
+
match c.arr_fwd.get(&addr) {
|
|
103
|
+
Some((w, t)) if w.upgrade().is_some_and(|up| Rc::ptr_eq(&up, arr)) => {
|
|
104
|
+
t.clone()
|
|
105
|
+
}
|
|
106
|
+
_ => {
|
|
107
|
+
c.maybe_sweep();
|
|
108
|
+
let t: VmRef<Vec<CoreValue>> = VmRef::new(Vec::new());
|
|
109
|
+
c.arr_fwd.insert(addr, (Rc::downgrade(arr), t.clone()));
|
|
110
|
+
c.arr_rev.insert(t.as_ptr() as usize, Rc::downgrade(arr));
|
|
111
|
+
t
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
BRIDGE.with(|c| c.borrow_mut().in_flight.insert(addr));
|
|
116
|
+
let contents = (|| -> Result<Vec<CoreValue>, String> {
|
|
117
|
+
let mut out = Vec::new();
|
|
118
|
+
for item in arr.borrow().iter() {
|
|
119
|
+
out.push(eval_to_core(item)?);
|
|
120
|
+
}
|
|
121
|
+
Ok(out)
|
|
122
|
+
})();
|
|
123
|
+
BRIDGE.with(|c| {
|
|
124
|
+
c.borrow_mut().in_flight.remove(&addr);
|
|
125
|
+
});
|
|
126
|
+
*twin.borrow_mut() = contents?;
|
|
127
|
+
Ok(CoreValue::Array(twin))
|
|
27
128
|
}
|
|
28
129
|
Value::Object(map) => {
|
|
29
|
-
let
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
130
|
+
let addr = Rc::as_ptr(map) as usize;
|
|
131
|
+
// Cycle guard — see the Array arm.
|
|
132
|
+
if let Some(twin) = BRIDGE.with(|c| {
|
|
133
|
+
let c = c.borrow();
|
|
134
|
+
if c.in_flight.contains(&addr) {
|
|
135
|
+
c.obj_fwd
|
|
136
|
+
.get(&addr)
|
|
137
|
+
.filter(|(w, _)| w.upgrade().is_some_and(|up| Rc::ptr_eq(&up, map)))
|
|
138
|
+
.map(|(_, t)| t.clone())
|
|
139
|
+
} else {
|
|
140
|
+
None
|
|
141
|
+
}
|
|
142
|
+
}) {
|
|
143
|
+
return Ok(CoreValue::Object(twin));
|
|
33
144
|
}
|
|
34
|
-
let
|
|
35
|
-
let mut
|
|
36
|
-
|
|
37
|
-
|
|
145
|
+
let twin = BRIDGE.with(|c| {
|
|
146
|
+
let mut c = c.borrow_mut();
|
|
147
|
+
match c.obj_fwd.get(&addr) {
|
|
148
|
+
Some((w, t)) if w.upgrade().is_some_and(|up| Rc::ptr_eq(&up, map)) => {
|
|
149
|
+
t.clone()
|
|
150
|
+
}
|
|
151
|
+
_ => {
|
|
152
|
+
c.maybe_sweep();
|
|
153
|
+
let t: VmRef<ObjectData> = VmRef::new(ObjectData {
|
|
154
|
+
strings: tishlang_core::PropMap::default(),
|
|
155
|
+
symbols: None,
|
|
156
|
+
frozen: false,
|
|
157
|
+
});
|
|
158
|
+
c.obj_fwd.insert(addr, (Rc::downgrade(map), t.clone()));
|
|
159
|
+
c.obj_rev.insert(t.as_ptr() as usize, Rc::downgrade(map));
|
|
160
|
+
t
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
BRIDGE.with(|c| c.borrow_mut().in_flight.insert(addr));
|
|
165
|
+
let contents = (|| -> Result<ObjectData, String> {
|
|
166
|
+
let b = map.borrow();
|
|
167
|
+
let mut strings = tishlang_core::PropMap::default();
|
|
168
|
+
for (k, v) in b.strings.iter() {
|
|
169
|
+
strings.insert(Arc::clone(k), eval_to_core(v)?);
|
|
38
170
|
}
|
|
39
|
-
Some(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
171
|
+
let symbols = if let Some(ss) = &b.symbols {
|
|
172
|
+
let mut sm = AHashMap::default();
|
|
173
|
+
for (id, v) in ss.iter() {
|
|
174
|
+
sm.insert(*id, eval_to_core(v)?);
|
|
175
|
+
}
|
|
176
|
+
Some(sm)
|
|
177
|
+
} else {
|
|
178
|
+
None
|
|
179
|
+
};
|
|
180
|
+
Ok(ObjectData { strings, symbols, frozen: false })
|
|
181
|
+
})();
|
|
182
|
+
BRIDGE.with(|c| {
|
|
183
|
+
c.borrow_mut().in_flight.remove(&addr);
|
|
184
|
+
});
|
|
185
|
+
*twin.borrow_mut() = contents?;
|
|
186
|
+
Ok(CoreValue::Object(twin))
|
|
44
187
|
}
|
|
45
188
|
Value::Symbol(s) => Ok(CoreValue::Symbol(Arc::clone(s))),
|
|
46
189
|
Value::Opaque(o) => Ok(CoreValue::Opaque(Arc::clone(o))),
|
|
@@ -59,6 +202,21 @@ pub fn core_to_eval(v: CoreValue) -> Value {
|
|
|
59
202
|
CoreValue::Bool(b) => Value::Bool(b),
|
|
60
203
|
CoreValue::Null => Value::Null,
|
|
61
204
|
CoreValue::Array(arr) => {
|
|
205
|
+
// #466: a forward-bridged twin flowing back out returns the ORIGINAL eval array, so
|
|
206
|
+
// identity survives the round trip (`s.has(a)` after `[...s.keys()]`, `x === a`).
|
|
207
|
+
if let Some(orig) = BRIDGE.with(|c| {
|
|
208
|
+
let c = c.borrow();
|
|
209
|
+
c.arr_rev
|
|
210
|
+
.get(&(arr.as_ptr() as usize))
|
|
211
|
+
.and_then(|w| w.upgrade())
|
|
212
|
+
.filter(|orig| {
|
|
213
|
+
c.arr_fwd
|
|
214
|
+
.get(&(Rc::as_ptr(orig) as usize))
|
|
215
|
+
.is_some_and(|(_, t)| VmRef::ptr_eq(t, &arr))
|
|
216
|
+
})
|
|
217
|
+
}) {
|
|
218
|
+
return Value::Array(orig);
|
|
219
|
+
}
|
|
62
220
|
let mut out = Vec::new();
|
|
63
221
|
for item in arr.borrow().iter() {
|
|
64
222
|
out.push(core_to_eval(item.clone()));
|
|
@@ -66,6 +224,20 @@ pub fn core_to_eval(v: CoreValue) -> Value {
|
|
|
66
224
|
Value::Array(Rc::new(RefCell::new(out)))
|
|
67
225
|
}
|
|
68
226
|
CoreValue::Object(map) => {
|
|
227
|
+
// #466: see the Array arm.
|
|
228
|
+
if let Some(orig) = BRIDGE.with(|c| {
|
|
229
|
+
let c = c.borrow();
|
|
230
|
+
c.obj_rev
|
|
231
|
+
.get(&(map.as_ptr() as usize))
|
|
232
|
+
.and_then(|w| w.upgrade())
|
|
233
|
+
.filter(|orig| {
|
|
234
|
+
c.obj_fwd
|
|
235
|
+
.get(&(Rc::as_ptr(orig) as usize))
|
|
236
|
+
.is_some_and(|(_, t)| VmRef::ptr_eq(t, &map))
|
|
237
|
+
})
|
|
238
|
+
}) {
|
|
239
|
+
return Value::Object(orig);
|
|
240
|
+
}
|
|
69
241
|
let b = map.borrow();
|
|
70
242
|
let mut out = PropMap::default();
|
|
71
243
|
for (k, v) in b.strings.iter() {
|
|
@@ -89,8 +261,17 @@ pub fn core_to_eval(v: CoreValue) -> Value {
|
|
|
89
261
|
CoreValue::Promise(_) => Value::Null,
|
|
90
262
|
// NumberArray: materialize to boxed Array for the interpreter (it has no packed path).
|
|
91
263
|
CoreValue::NumberArray(arr) => {
|
|
92
|
-
|
|
93
|
-
|
|
264
|
+
// Materialize to a boxed Array for the interpreter (it has no packed path). `to_values`
|
|
265
|
+
// handles a packed or a deopted (#199) backing.
|
|
266
|
+
let out: Vec<Value> = arr
|
|
267
|
+
.borrow()
|
|
268
|
+
.to_values()
|
|
269
|
+
.into_iter()
|
|
270
|
+
.map(|v| match v {
|
|
271
|
+
tishlang_core::Value::Number(n) => Value::Number(n),
|
|
272
|
+
_ => core_to_eval(v),
|
|
273
|
+
})
|
|
274
|
+
.collect();
|
|
94
275
|
Value::Array(Rc::new(RefCell::new(out)))
|
|
95
276
|
}
|
|
96
277
|
// `CoreNativeFn` is feature-gated (Rc vs Arc), so use Clone::clone
|
|
@@ -115,3 +296,117 @@ pub fn core_to_eval(v: CoreValue) -> Value {
|
|
|
115
296
|
}
|
|
116
297
|
}
|
|
117
298
|
}
|
|
299
|
+
|
|
300
|
+
#[cfg(test)]
|
|
301
|
+
mod bridge_identity_tests {
|
|
302
|
+
use super::*;
|
|
303
|
+
|
|
304
|
+
fn eval_obj(pairs: &[(&str, Value)]) -> Value {
|
|
305
|
+
let mut strings = PropMap::default();
|
|
306
|
+
for (k, v) in pairs {
|
|
307
|
+
strings.insert(Arc::from(*k), v.clone());
|
|
308
|
+
}
|
|
309
|
+
Value::Object(Rc::new(RefCell::new(EvalObjectData::from_strings(strings))))
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/// #466: the same eval object must bridge to the SAME core allocation every time —
|
|
313
|
+
/// that's what makes it usable as a Map/Set key (SameValueZero is ptr_eq for objects).
|
|
314
|
+
#[test]
|
|
315
|
+
fn same_eval_object_bridges_to_same_core_twin() {
|
|
316
|
+
let k = eval_obj(&[("id", Value::Number(1.0))]);
|
|
317
|
+
let (CoreValue::Object(t1), CoreValue::Object(t2)) =
|
|
318
|
+
(eval_to_core(&k).unwrap(), eval_to_core(&k).unwrap())
|
|
319
|
+
else {
|
|
320
|
+
panic!("expected core objects");
|
|
321
|
+
};
|
|
322
|
+
assert!(VmRef::ptr_eq(&t1, &t2), "twin identity must be stable across bridges");
|
|
323
|
+
|
|
324
|
+
// Distinct eval objects get distinct twins.
|
|
325
|
+
let k2 = eval_obj(&[("id", Value::Number(1.0))]);
|
|
326
|
+
let CoreValue::Object(t3) = eval_to_core(&k2).unwrap() else { panic!() };
|
|
327
|
+
assert!(!VmRef::ptr_eq(&t1, &t3), "distinct objects must not share a twin");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/// Re-bridging refreshes the twin's contents (copy-at-the-boundary semantics kept).
|
|
331
|
+
#[test]
|
|
332
|
+
fn rebridge_refreshes_twin_contents() {
|
|
333
|
+
let k = eval_obj(&[("id", Value::Number(1.0))]);
|
|
334
|
+
let CoreValue::Object(t1) = eval_to_core(&k).unwrap() else { panic!() };
|
|
335
|
+
assert!(matches!(t1.borrow().strings.get("id"), Some(CoreValue::Number(n)) if *n == 1.0));
|
|
336
|
+
|
|
337
|
+
if let Value::Object(o) = &k {
|
|
338
|
+
o.borrow_mut().strings.insert(Arc::from("id"), Value::Number(42.0));
|
|
339
|
+
}
|
|
340
|
+
let CoreValue::Object(t2) = eval_to_core(&k).unwrap() else { panic!() };
|
|
341
|
+
assert!(VmRef::ptr_eq(&t1, &t2));
|
|
342
|
+
assert!(matches!(t2.borrow().strings.get("id"), Some(CoreValue::Number(n)) if *n == 42.0));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/// A forward-bridged twin converts BACK to the original eval object (round-trip identity:
|
|
346
|
+
/// `m.get(k) === v` / `[...m.keys()][0] === k`).
|
|
347
|
+
#[test]
|
|
348
|
+
fn core_to_eval_returns_original_for_bridged_twin() {
|
|
349
|
+
let k = eval_obj(&[("id", Value::Number(1.0))]);
|
|
350
|
+
let core = eval_to_core(&k).unwrap();
|
|
351
|
+
let back = core_to_eval(core);
|
|
352
|
+
let (Value::Object(orig), Value::Object(round)) = (&k, &back) else { panic!() };
|
|
353
|
+
assert!(Rc::ptr_eq(orig, round), "round trip must return the original eval object");
|
|
354
|
+
|
|
355
|
+
// An unrelated core object (never forward-bridged) still gets a fresh conversion.
|
|
356
|
+
let alien = CoreValue::Object(VmRef::new(ObjectData {
|
|
357
|
+
strings: tishlang_core::PropMap::default(),
|
|
358
|
+
symbols: None,
|
|
359
|
+
frozen: false,
|
|
360
|
+
}));
|
|
361
|
+
assert!(matches!(core_to_eval(alien), Value::Object(_)));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/// Arrays get the same treatment (Set members / Map values).
|
|
365
|
+
#[test]
|
|
366
|
+
fn array_identity_and_roundtrip() {
|
|
367
|
+
let a = Value::Array(Rc::new(RefCell::new(vec![Value::Number(1.0), Value::Number(2.0)])));
|
|
368
|
+
let (CoreValue::Array(t1), CoreValue::Array(t2)) =
|
|
369
|
+
(eval_to_core(&a).unwrap(), eval_to_core(&a).unwrap())
|
|
370
|
+
else {
|
|
371
|
+
panic!();
|
|
372
|
+
};
|
|
373
|
+
assert!(VmRef::ptr_eq(&t1, &t2));
|
|
374
|
+
let back = core_to_eval(CoreValue::Array(t1));
|
|
375
|
+
let (Value::Array(orig), Value::Array(round)) = (&a, &back) else { panic!() };
|
|
376
|
+
assert!(Rc::ptr_eq(orig, round));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/// A self-referential object must terminate (insert-before-recurse) and its twin's
|
|
380
|
+
/// self-property must be the twin itself — mirroring the eval-side cycle.
|
|
381
|
+
#[test]
|
|
382
|
+
fn cyclic_object_converts_without_recursing_forever() {
|
|
383
|
+
let k = eval_obj(&[]);
|
|
384
|
+
if let Value::Object(o) = &k {
|
|
385
|
+
let self_ref = k.clone();
|
|
386
|
+
o.borrow_mut().strings.insert(Arc::from("me"), self_ref);
|
|
387
|
+
}
|
|
388
|
+
let CoreValue::Object(twin) = eval_to_core(&k).unwrap() else { panic!() };
|
|
389
|
+
let inner = twin.borrow().strings.get("me").cloned();
|
|
390
|
+
let Some(CoreValue::Object(inner)) = inner else { panic!("self prop missing") };
|
|
391
|
+
assert!(VmRef::ptr_eq(&twin, &inner), "cycle must map to the twin itself");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/// Dropping the eval object invalidates its cache entry: a NEW object (even if the allocator
|
|
395
|
+
/// reuses the address) must get a fresh twin, never the stale one (the #250 lesson).
|
|
396
|
+
#[test]
|
|
397
|
+
fn dead_eval_object_never_resurrects_stale_twin() {
|
|
398
|
+
let mut stale_twin = None;
|
|
399
|
+
for _ in 0..64 {
|
|
400
|
+
let k = eval_obj(&[("x", Value::Number(7.0))]);
|
|
401
|
+
let CoreValue::Object(t) = eval_to_core(&k).unwrap() else { panic!() };
|
|
402
|
+
if let Some(prev) = &stale_twin {
|
|
403
|
+
assert!(
|
|
404
|
+
!VmRef::ptr_eq(prev, &t),
|
|
405
|
+
"a fresh eval object must never receive a dead object's twin"
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
stale_twin = Some(t);
|
|
409
|
+
// k drops here — its address is free for reuse by the next iteration.
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "tishlang_lsp"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.43.8"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "Language Server Protocol implementation for Tish"
|
|
6
6
|
license-file = { workspace = true }
|
|
@@ -29,3 +29,5 @@ walkdir = "2"
|
|
|
29
29
|
# implements `Service` against (0.4), or the trait bound on `.call()` won't be satisfied. `util`
|
|
30
30
|
# brings in `ServiceExt::ready`.
|
|
31
31
|
tower = { version = "0.4", features = ["util"] }
|
|
32
|
+
# Temp dirs for the platform-cascade import-resolution test.
|
|
33
|
+
tempfile = "3"
|
|
@@ -451,19 +451,29 @@ fn module_source(can: &Path, u: &Url, open_docs: &HashMap<Url, String>) -> Optio
|
|
|
451
451
|
}
|
|
452
452
|
}
|
|
453
453
|
|
|
454
|
-
fn
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
454
|
+
fn resolve_relative_module_path(from_dir: &Path, from_s: &str) -> Option<std::path::PathBuf> {
|
|
455
|
+
let _ = tishlang_compile::apply_resolve_env(None, None);
|
|
456
|
+
let ctx = tishlang_compile::resolve_context();
|
|
457
|
+
if let Some(p) = tishlang_compile::resolve_with_platform(from_s, from_dir, ctx) {
|
|
458
|
+
return Some(p);
|
|
459
|
+
}
|
|
460
|
+
// Fallback: legacy exact `.tish` join (no cascade).
|
|
460
461
|
let target = from_dir.join(from_s.trim_start_matches("./"));
|
|
461
462
|
let target = if target.extension().is_none() {
|
|
462
463
|
target.with_extension("tish")
|
|
463
464
|
} else {
|
|
464
465
|
target
|
|
465
466
|
};
|
|
466
|
-
|
|
467
|
+
target.canonicalize().ok()
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
fn resolve_relative_tish(
|
|
471
|
+
from_dir: &Path,
|
|
472
|
+
from_s: &str,
|
|
473
|
+
imported: &str,
|
|
474
|
+
open_docs: &HashMap<Url, String>,
|
|
475
|
+
) -> Option<Location> {
|
|
476
|
+
let can = resolve_relative_module_path(from_dir, from_s)?;
|
|
467
477
|
let u = Url::from_file_path(&can).ok()?;
|
|
468
478
|
let src = module_source(&can, &u, open_docs)?;
|
|
469
479
|
let prog = tishlang_parser::parse(&src).ok()?;
|
|
@@ -476,13 +486,7 @@ fn resolve_relative_tish_default(
|
|
|
476
486
|
from_s: &str,
|
|
477
487
|
open_docs: &HashMap<Url, String>,
|
|
478
488
|
) -> Option<Location> {
|
|
479
|
-
let
|
|
480
|
-
let target = if target.extension().is_none() {
|
|
481
|
-
target.with_extension("tish")
|
|
482
|
-
} else {
|
|
483
|
-
target
|
|
484
|
-
};
|
|
485
|
-
let can = target.canonicalize().ok()?;
|
|
489
|
+
let can = resolve_relative_module_path(from_dir, from_s)?;
|
|
486
490
|
let u = Url::from_file_path(&can).ok()?;
|
|
487
491
|
let src = module_source(&can, &u, open_docs)?;
|
|
488
492
|
let prog = tishlang_parser::parse(&src).ok()?;
|
|
@@ -628,4 +632,27 @@ mod receiver_member_tests {
|
|
|
628
632
|
let k = super::pragma_key_for_native_member(Some("window"), &[Arc::from("innerHeight")]);
|
|
629
633
|
assert_eq!(k.as_deref(), Some("window.innerHeight"));
|
|
630
634
|
}
|
|
635
|
+
|
|
636
|
+
// Goto-def resolves relative imports through the platform/surface cascade (same rules as
|
|
637
|
+
// `tish resolve-id` / disk resolve), falling back to the bare `.tish` when no context is set.
|
|
638
|
+
#[test]
|
|
639
|
+
fn resolve_relative_module_path_honors_platform_cascade() {
|
|
640
|
+
use tishlang_compile::{set_resolve_context, Platform, ResolveContext, Surface};
|
|
641
|
+
let dir = tempfile::tempdir().unwrap();
|
|
642
|
+
let root = dir.path();
|
|
643
|
+
std::fs::write(root.join("Button.tish"), "export fn Button() {}").unwrap();
|
|
644
|
+
std::fs::write(root.join("Button.macos.tish"), "export fn Button() {}").unwrap();
|
|
645
|
+
|
|
646
|
+
set_resolve_context(ResolveContext {
|
|
647
|
+
platform: Platform::Macos,
|
|
648
|
+
surface: Surface::Native,
|
|
649
|
+
});
|
|
650
|
+
let hit = super::resolve_relative_module_path(root, "./Button").expect("macos resolve");
|
|
651
|
+
assert!(hit.ends_with("Button.macos.tish"));
|
|
652
|
+
|
|
653
|
+
// No platform context → legacy bare `.tish`.
|
|
654
|
+
set_resolve_context(ResolveContext::default());
|
|
655
|
+
let base = super::resolve_relative_module_path(root, "./Button").expect("base resolve");
|
|
656
|
+
assert!(base.ends_with("Button.tish"));
|
|
657
|
+
}
|
|
631
658
|
}
|