@tishlang/tish 2.2.5 → 2.8.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/bin/tish +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +5 -1
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/cli_help.rs +12 -0
- package/crates/tish/src/main.rs +69 -11
- package/crates/tish_ast/src/ast.rs +12 -5
- package/crates/tish_build_utils/src/lib.rs +37 -0
- package/crates/tish_builtins/src/array.rs +82 -1
- package/crates/tish_builtins/src/globals.rs +50 -16
- package/crates/tish_builtins/src/math.rs +63 -9
- package/crates/tish_builtins/src/number.rs +20 -1
- package/crates/tish_builtins/src/string.rs +68 -4
- package/crates/tish_builtins/src/typedarrays.rs +23 -16
- package/crates/tish_bytecode/src/compiler.rs +94 -28
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +19 -0
- package/crates/tish_compile/src/check.rs +11 -10
- package/crates/tish_compile/src/codegen.rs +1386 -42
- package/crates/tish_compile/src/infer.rs +16 -16
- package/crates/tish_compile/src/lib.rs +38 -0
- package/crates/tish_compile/src/resolve.rs +3 -2
- package/crates/tish_compile/src/types.rs +26 -9
- package/crates/tish_compile_js/src/codegen.rs +55 -4
- package/crates/tish_compile_js/src/tests_jsx.rs +44 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +1 -0
- package/crates/tish_core/Cargo.toml +2 -0
- package/crates/tish_core/src/json.rs +135 -34
- package/crates/tish_core/src/lib.rs +23 -1
- package/crates/tish_core/src/value.rs +64 -11
- package/crates/tish_eval/src/eval.rs +144 -197
- package/crates/tish_eval/src/natives.rs +45 -45
- package/crates/tish_eval/src/regex.rs +35 -27
- package/crates/tish_eval/src/value.rs +8 -0
- package/crates/tish_fmt/src/lib.rs +46 -2
- package/crates/tish_lint/src/bin/tish-lint.rs +17 -8
- package/crates/tish_lint/src/lib.rs +197 -21
- package/crates/tish_lsp/Cargo.toml +7 -1
- package/crates/tish_lsp/src/import_goto.rs +52 -7
- package/crates/tish_lsp/src/main.rs +913 -145
- package/crates/tish_opt/src/lib.rs +5 -3
- package/crates/tish_parser/src/lib.rs +23 -5
- package/crates/tish_parser/src/parser.rs +35 -35
- package/crates/tish_resolve/src/lib.rs +567 -17
- package/crates/tish_runtime/src/lib.rs +58 -18
- package/crates/tish_ui/src/jsx.rs +2 -2
- package/crates/tish_vm/src/jit.rs +212 -10
- package/crates/tish_vm/src/vm.rs +39 -18
- package/crates/tish_wasm/src/lib.rs +116 -22
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -136,7 +136,14 @@ fn construct(kind: Kind, args: &[Value]) -> Value {
|
|
|
136
136
|
/// construction-only-coercion gap for this one view). Any op without a packed fast path materialises
|
|
137
137
|
/// it back to a boxed array, so every array method keeps working.
|
|
138
138
|
pub fn float64_array_packed(args: &[Value]) -> Value {
|
|
139
|
-
|
|
139
|
+
float64_array_with(Value::packed_arrays_enabled(), args)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// `float64_array_packed` with the packed/boxed choice made explicit, so tests exercise both paths
|
|
143
|
+
/// without toggling the now-cached process env flag (#166). `packed = false` is byte-identical to
|
|
144
|
+
/// the generic boxed `Value::Array` constructor.
|
|
145
|
+
fn float64_array_with(packed: bool, args: &[Value]) -> Value {
|
|
146
|
+
if !packed {
|
|
140
147
|
// Byte-identical fallback to the generic boxed `Value::Array` backing.
|
|
141
148
|
return construct(Kind::F64, args);
|
|
142
149
|
}
|
|
@@ -261,25 +268,26 @@ mod tests {
|
|
|
261
268
|
assert_eq!(nums(&v), vec![0.0, 1.0]);
|
|
262
269
|
}
|
|
263
270
|
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
271
|
+
// The packed/boxed selection is passed explicitly via `float64_array_with`, so this exercises
|
|
272
|
+
// both paths without touching the now-cached process env flag (#166) — no env mutation, no
|
|
273
|
+
// mid-test toggle race, parallel-safe.
|
|
267
274
|
#[test]
|
|
268
275
|
fn float64_packed_respects_flag() {
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
let boxed = float64_array_packed(&[Value::Number(3.0)]);
|
|
276
|
+
// Packed off (the default): byte-identical boxed `Value::Array` fallback.
|
|
277
|
+
let boxed = float64_array_with(false, &[Value::Number(3.0)]);
|
|
272
278
|
assert!(matches!(boxed, Value::Array(_)), "packed-off must return boxed Array");
|
|
273
279
|
assert_eq!(nums(&boxed), vec![0.0, 0.0, 0.0]);
|
|
274
280
|
|
|
275
|
-
//
|
|
281
|
+
// Packed on: `Value::NumberArray`. F64 needs no coercion (exact), non-numeric → NaN
|
|
276
282
|
// (matching the boxed `from_values(F64, …)`), and the length form zero-fills.
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
Value::
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
+
let packed = float64_array_with(
|
|
284
|
+
true,
|
|
285
|
+
&[Value::Array(VmRef::new(vec![
|
|
286
|
+
Value::Number(1.1),
|
|
287
|
+
Value::Number(2.2),
|
|
288
|
+
Value::Null,
|
|
289
|
+
]))],
|
|
290
|
+
);
|
|
283
291
|
match &packed {
|
|
284
292
|
Value::NumberArray(a) => {
|
|
285
293
|
let v = a.borrow();
|
|
@@ -290,9 +298,8 @@ mod tests {
|
|
|
290
298
|
_ => panic!("packed-on must return NumberArray"),
|
|
291
299
|
}
|
|
292
300
|
assert!(matches!(
|
|
293
|
-
|
|
301
|
+
float64_array_with(true, &[Value::Number(2.0)]),
|
|
294
302
|
Value::NumberArray(_)
|
|
295
303
|
));
|
|
296
|
-
std::env::remove_var("TISH_PACKED_ARRAYS");
|
|
297
304
|
}
|
|
298
305
|
}
|
|
@@ -157,7 +157,7 @@ fn expr_is_param_only(e: &Expr, params: &HashSet<&str>) -> bool {
|
|
|
157
157
|
ArrayElement::Spread(_) => false,
|
|
158
158
|
}),
|
|
159
159
|
Expr::Object { props, .. } => props.iter().all(|p| match p {
|
|
160
|
-
ObjectProp::KeyValue(_, x) => expr_is_param_only(x, params),
|
|
160
|
+
ObjectProp::KeyValue(_, x, _) => expr_is_param_only(x, params),
|
|
161
161
|
ObjectProp::Spread(_) => false,
|
|
162
162
|
}),
|
|
163
163
|
Expr::TemplateLiteral { exprs, .. } => {
|
|
@@ -332,7 +332,7 @@ fn expr_rebinds(e: &Expr, name: &str) -> bool {
|
|
|
332
332
|
ArrayElement::Expr(e) | ArrayElement::Spread(e) => expr_rebinds(e, name),
|
|
333
333
|
}),
|
|
334
334
|
Expr::Object { props, .. } => props.iter().any(|p| match p {
|
|
335
|
-
ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => expr_rebinds(e, name),
|
|
335
|
+
ObjectProp::KeyValue(_, e, _) | ObjectProp::Spread(e) => expr_rebinds(e, name),
|
|
336
336
|
}),
|
|
337
337
|
Expr::MemberAssign { object, value, .. } => expr_rebinds(object, name) || expr_rebinds(value, name),
|
|
338
338
|
Expr::IndexAssign { object, index, value, .. } => {
|
|
@@ -452,7 +452,7 @@ impl SlotScan {
|
|
|
452
452
|
ArrayElement::Expr(e) | ArrayElement::Spread(e) => self.expr(e, in_closure),
|
|
453
453
|
}),
|
|
454
454
|
Expr::Object { props, .. } => props.iter().all(|p| match p {
|
|
455
|
-
ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => self.expr(e, in_closure),
|
|
455
|
+
ObjectProp::KeyValue(_, e, _) | ObjectProp::Spread(e) => self.expr(e, in_closure),
|
|
456
456
|
}),
|
|
457
457
|
Expr::Assign { name, value, .. }
|
|
458
458
|
| Expr::CompoundAssign { name, value, .. }
|
|
@@ -836,6 +836,53 @@ impl<'a> Compiler<'a> {
|
|
|
836
836
|
self.chunk.code[patch_pos + 1] = bytes[1];
|
|
837
837
|
}
|
|
838
838
|
|
|
839
|
+
/// Compile `cond` in CONDITION position (if / while / for / do-while). The operand stack is EMPTY
|
|
840
|
+
/// at every emitted `JumpIfFalse` — `&&` / `||` lower to pure control flow rather than the
|
|
841
|
+
/// value-producing `Dup` + `BinOp` form, so a hot numeric loop whose only disqualifier was the
|
|
842
|
+
/// condition shape stays eligible for the cranelift JIT (#167). Returns the `JumpIfFalse` patch
|
|
843
|
+
/// sites the caller must point at its "condition is false" target. The condition value itself is
|
|
844
|
+
/// never observed here, so truthiness-only lowering is exact; short-circuit order is preserved.
|
|
845
|
+
fn compile_condition_jump_if_false(
|
|
846
|
+
&mut self,
|
|
847
|
+
cond: &Expr,
|
|
848
|
+
) -> Result<Vec<usize>, CompileError> {
|
|
849
|
+
match cond {
|
|
850
|
+
// `a && b` is false if EITHER operand is false — test each in order; both exit to the
|
|
851
|
+
// same false-target, so concatenate their patch sites.
|
|
852
|
+
Expr::Binary {
|
|
853
|
+
left,
|
|
854
|
+
op: BinOp::And,
|
|
855
|
+
right,
|
|
856
|
+
..
|
|
857
|
+
} => {
|
|
858
|
+
let mut patches = self.compile_condition_jump_if_false(left)?;
|
|
859
|
+
patches.extend(self.compile_condition_jump_if_false(right)?);
|
|
860
|
+
Ok(patches)
|
|
861
|
+
}
|
|
862
|
+
// `a || b`: a truthy left makes the condition hold (skip the right); only the right's
|
|
863
|
+
// falsiness exits.
|
|
864
|
+
Expr::Binary {
|
|
865
|
+
left,
|
|
866
|
+
op: BinOp::Or,
|
|
867
|
+
right,
|
|
868
|
+
..
|
|
869
|
+
} => {
|
|
870
|
+
self.compile_expr(left)?;
|
|
871
|
+
let take_right = self.emit_jump(Opcode::JumpIfFalse); // left falsy → evaluate right
|
|
872
|
+
let done = self.emit_jump(Opcode::Jump); // left truthy → condition holds
|
|
873
|
+
self.patch_jump(take_right, self.chunk.code.len());
|
|
874
|
+
let patches = self.compile_condition_jump_if_false(right)?;
|
|
875
|
+
self.patch_jump(done, self.chunk.code.len());
|
|
876
|
+
Ok(patches)
|
|
877
|
+
}
|
|
878
|
+
// Any other expression: evaluate it; one JumpIfFalse consumes the value.
|
|
879
|
+
_ => {
|
|
880
|
+
self.compile_expr(cond)?;
|
|
881
|
+
Ok(vec![self.emit_jump(Opcode::JumpIfFalse)])
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
839
886
|
/// Patch a JumpBack operand: distance from the IP after this insn back to `target`.
|
|
840
887
|
/// `patch_pos` is the first byte of the u16 operand (same as [`Self::emit_jump_back`]'s return value).
|
|
841
888
|
fn patch_jump_back(&mut self, patch_pos: usize, target: usize) {
|
|
@@ -1194,11 +1241,13 @@ impl<'a> Compiler<'a> {
|
|
|
1194
1241
|
else_branch,
|
|
1195
1242
|
..
|
|
1196
1243
|
} => {
|
|
1197
|
-
self.
|
|
1198
|
-
let jump_else = self.emit_jump(Opcode::JumpIfFalse);
|
|
1244
|
+
let jump_else_sites = self.compile_condition_jump_if_false(cond)?;
|
|
1199
1245
|
self.compile_statement(then_branch)?;
|
|
1200
1246
|
let jump_end = self.emit_jump(Opcode::Jump);
|
|
1201
|
-
|
|
1247
|
+
let else_target = self.chunk.code.len();
|
|
1248
|
+
for site in &jump_else_sites {
|
|
1249
|
+
self.patch_jump(*site, else_target);
|
|
1250
|
+
}
|
|
1202
1251
|
if let Some(else_s) = else_branch {
|
|
1203
1252
|
self.compile_statement(else_s)?;
|
|
1204
1253
|
}
|
|
@@ -1222,14 +1271,16 @@ impl<'a> Compiler<'a> {
|
|
|
1222
1271
|
self.breakable_stack.push(Breakable::Loop {
|
|
1223
1272
|
unwind_depth: self.block_depth,
|
|
1224
1273
|
});
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1274
|
+
// Condition-position lowering: empty stack at each JumpIfFalse keeps the loop
|
|
1275
|
+
// JIT-eligible (#167). Each returned site exits to `end`.
|
|
1276
|
+
let jump_out_sites = self.compile_condition_jump_if_false(cond)?;
|
|
1228
1277
|
self.compile_statement(body)?;
|
|
1229
1278
|
let jump_back_dist = (self.chunk.code.len() + 3).saturating_sub(start);
|
|
1230
1279
|
self.emit_u16(Opcode::JumpBack, jump_back_dist as u16);
|
|
1231
1280
|
let end = self.chunk.code.len();
|
|
1232
|
-
|
|
1281
|
+
for site in &jump_out_sites {
|
|
1282
|
+
self.patch_jump(*site, end);
|
|
1283
|
+
}
|
|
1233
1284
|
let info = self.loop_stack.pop().unwrap();
|
|
1234
1285
|
self.breakable_stack.pop();
|
|
1235
1286
|
for p in info.continue_patches {
|
|
@@ -1266,14 +1317,16 @@ impl<'a> Compiler<'a> {
|
|
|
1266
1317
|
self.emit_u16(Opcode::LoopVarsBegin, idx);
|
|
1267
1318
|
}
|
|
1268
1319
|
let cond_start = self.chunk.code.len();
|
|
1269
|
-
|
|
1270
|
-
|
|
1320
|
+
// Condition-position lowering keeps the loop JIT-eligible (#167). The absent-condition
|
|
1321
|
+
// `for (;;)` keeps its `true` constant + single exit jump.
|
|
1322
|
+
let jump_out_sites = if let Some(c) = cond {
|
|
1323
|
+
self.compile_condition_jump_if_false(c)?
|
|
1271
1324
|
} else {
|
|
1272
1325
|
let idx = self.constant_idx(Constant::Bool(true));
|
|
1273
1326
|
self.emit(Opcode::LoadConst);
|
|
1274
1327
|
self.chunk.write_u16(idx);
|
|
1275
|
-
|
|
1276
|
-
|
|
1328
|
+
vec![self.emit_jump(Opcode::JumpIfFalse)]
|
|
1329
|
+
};
|
|
1277
1330
|
self.loop_stack.push(LoopInfo {
|
|
1278
1331
|
break_patches: Vec::new(),
|
|
1279
1332
|
continue_patches: Vec::new(),
|
|
@@ -1295,7 +1348,9 @@ impl<'a> Compiler<'a> {
|
|
|
1295
1348
|
let jump_back_dist = (self.chunk.code.len() + 3).saturating_sub(cond_start);
|
|
1296
1349
|
self.emit_u16(Opcode::JumpBack, jump_back_dist as u16);
|
|
1297
1350
|
let end = self.chunk.code.len();
|
|
1298
|
-
|
|
1351
|
+
for site in &jump_out_sites {
|
|
1352
|
+
self.patch_jump(*site, end);
|
|
1353
|
+
}
|
|
1299
1354
|
for p in info.break_patches {
|
|
1300
1355
|
self.patch_jump(p, end);
|
|
1301
1356
|
}
|
|
@@ -1561,23 +1616,32 @@ impl<'a> Compiler<'a> {
|
|
|
1561
1616
|
self.loop_stack.push(LoopInfo {
|
|
1562
1617
|
break_patches: Vec::new(),
|
|
1563
1618
|
continue_patches: Vec::new(),
|
|
1564
|
-
|
|
1619
|
+
// `continue` jumps to the condition test, which is emitted AFTER the body — a
|
|
1620
|
+
// FORWARD jump. (A backward JumpBack would patch to dist = 0 via saturating_sub
|
|
1621
|
+
// of a forward target, becoming a no-op; execution then fell through and re-ran
|
|
1622
|
+
// the body's already-unwound ExitBlock on an empty block stack — the
|
|
1623
|
+
// "ExitBlock without matching EnterBlock" crash.)
|
|
1624
|
+
continue_is_forward_jump: true,
|
|
1565
1625
|
});
|
|
1566
1626
|
self.breakable_stack.push(Breakable::Loop {
|
|
1567
1627
|
unwind_depth: self.block_depth,
|
|
1568
1628
|
});
|
|
1569
1629
|
self.compile_statement(body)?;
|
|
1570
1630
|
let cond_start = self.chunk.code.len();
|
|
1571
|
-
|
|
1572
|
-
|
|
1631
|
+
// Condition-position lowering: a false condition exits to `end`, a true one falls
|
|
1632
|
+
// through to the JumpBack — JIT-eligible, no value left on the stack (#167).
|
|
1633
|
+
let exit_sites = self.compile_condition_jump_if_false(cond)?;
|
|
1573
1634
|
let jump_back_dist = (self.chunk.code.len() + 3).saturating_sub(start);
|
|
1574
1635
|
self.emit_u16(Opcode::JumpBack, jump_back_dist as u16);
|
|
1575
1636
|
let end = self.chunk.code.len();
|
|
1576
|
-
|
|
1637
|
+
for site in &exit_sites {
|
|
1638
|
+
self.patch_jump(*site, end);
|
|
1639
|
+
}
|
|
1577
1640
|
let info = self.loop_stack.pop().unwrap();
|
|
1578
1641
|
self.breakable_stack.pop();
|
|
1579
1642
|
for p in info.continue_patches {
|
|
1580
|
-
|
|
1643
|
+
// Forward jump to the condition (see continue_is_forward_jump above).
|
|
1644
|
+
self.patch_jump(p, cond_start);
|
|
1581
1645
|
}
|
|
1582
1646
|
for p in info.break_patches {
|
|
1583
1647
|
self.patch_jump(p, end);
|
|
@@ -1859,14 +1923,16 @@ impl<'a> Compiler<'a> {
|
|
|
1859
1923
|
} => {
|
|
1860
1924
|
match op {
|
|
1861
1925
|
BinOp::And => {
|
|
1862
|
-
// Short-circuit: a && b
|
|
1926
|
+
// Short-circuit + value-returning (JS): `a && b` is `a` when `a` is falsy,
|
|
1927
|
+
// else `b` — NOT a coerced boolean (#240). Mirror the `||` lowering below:
|
|
1928
|
+
// keep the left operand on a falsy short-circuit, discard it and evaluate the
|
|
1929
|
+
// right otherwise. (The old `BinOp(And)` here coerced a truthy-left result to
|
|
1930
|
+
// a Bool, so `five() && 7` yielded `true` instead of `7`.)
|
|
1863
1931
|
self.compile_expr(left)?;
|
|
1864
1932
|
self.emit(Opcode::Dup);
|
|
1865
|
-
let
|
|
1866
|
-
self.
|
|
1867
|
-
self.
|
|
1868
|
-
let jump_end = self.emit_jump(Opcode::Jump);
|
|
1869
|
-
self.patch_jump(jump_shortcut, self.chunk.code.len());
|
|
1933
|
+
let jump_end = self.emit_jump(Opcode::JumpIfFalse); // a falsy → keep a
|
|
1934
|
+
self.emit(Opcode::Pop); // a truthy → discard a …
|
|
1935
|
+
self.compile_expr(right)?; // … and yield b
|
|
1870
1936
|
self.patch_jump(jump_end, self.chunk.code.len());
|
|
1871
1937
|
}
|
|
1872
1938
|
BinOp::Or => {
|
|
@@ -2123,7 +2189,7 @@ impl<'a> Compiler<'a> {
|
|
|
2123
2189
|
self.emit_u16(Opcode::NewObject, 0); // start with {}
|
|
2124
2190
|
for prop in props {
|
|
2125
2191
|
match prop {
|
|
2126
|
-
ObjectProp::KeyValue(k, v) => {
|
|
2192
|
+
ObjectProp::KeyValue(k, v, _) => {
|
|
2127
2193
|
let idx = self.constant_idx(Constant::String(Arc::clone(k)));
|
|
2128
2194
|
self.emit(Opcode::LoadConst);
|
|
2129
2195
|
self.chunk.write_u16(idx);
|
|
@@ -2139,7 +2205,7 @@ impl<'a> Compiler<'a> {
|
|
|
2139
2205
|
}
|
|
2140
2206
|
} else {
|
|
2141
2207
|
for prop in props {
|
|
2142
|
-
if let ObjectProp::KeyValue(k, v) = prop {
|
|
2208
|
+
if let ObjectProp::KeyValue(k, v, _) = prop {
|
|
2143
2209
|
let idx = self.constant_idx(Constant::String(Arc::clone(k)));
|
|
2144
2210
|
self.emit(Opcode::LoadConst);
|
|
2145
2211
|
self.chunk.write_u16(idx);
|
|
@@ -42,3 +42,22 @@ fn mutation_vm_ast_opt_with_peephole() {
|
|
|
42
42
|
let chunk = compile(&prog).expect("compile");
|
|
43
43
|
run(&chunk).expect("VM");
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
// Regression: `continue` inside a `do { } while (cond)` targets the condition test, which is emitted
|
|
47
|
+
// AFTER the body — a FORWARD jump. It used to be lowered as a backward `JumpBack`, which `patch_jump_back`
|
|
48
|
+
// resolved to distance 0 (saturating_sub of a forward target) — a no-op. Execution then fell through into
|
|
49
|
+
// the body block's already-unwound `ExitBlock`, crashing the VM with "ExitBlock without matching
|
|
50
|
+
// EnterBlock". The `continue` here unwinds the body block AND the `if` then-block, so it exercises the
|
|
51
|
+
// multi-ExitBlock unwind path specifically.
|
|
52
|
+
#[test]
|
|
53
|
+
fn do_while_continue_does_not_crash_vm() {
|
|
54
|
+
let src = "let d = 0\n\
|
|
55
|
+
do {\n\
|
|
56
|
+
d = d + 1\n\
|
|
57
|
+
if (d === 2) { continue }\n\
|
|
58
|
+
} while (d < 5)\n";
|
|
59
|
+
let prog = tishlang_parser::parse(src).expect("parse");
|
|
60
|
+
// Both compile paths must run clean (the crash happened regardless of peephole optimization).
|
|
61
|
+
run(&compile_unoptimized(&prog).expect("compile (unopt)")).expect("VM run unoptimized");
|
|
62
|
+
run(&compile(&prog).expect("compile (opt)")).expect("VM run optimized");
|
|
63
|
+
}
|
|
@@ -29,6 +29,7 @@ fn lit_base(lit: &TypeLiteral) -> TypeAnnotation {
|
|
|
29
29
|
TypeLiteral::Bool(_) => "boolean",
|
|
30
30
|
}
|
|
31
31
|
.into(),
|
|
32
|
+
tishlang_ast::Span::default(),
|
|
32
33
|
)
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -70,19 +71,19 @@ pub fn check_program(program: &Program) -> Vec<TypeDiagnostic> {
|
|
|
70
71
|
// ── helpers: type constructors / predicates ─────────────────────────────────────────────────
|
|
71
72
|
|
|
72
73
|
fn simple(s: &str) -> TypeAnnotation {
|
|
73
|
-
TypeAnnotation::Simple(s.into())
|
|
74
|
+
TypeAnnotation::Simple(s.into(), tishlang_ast::Span::default())
|
|
74
75
|
}
|
|
75
76
|
fn is_any(ann: &TypeAnnotation) -> bool {
|
|
76
|
-
matches!(ann, TypeAnnotation::Simple(s) if s.as_ref() == "any")
|
|
77
|
+
matches!(ann, TypeAnnotation::Simple(s, _) if s.as_ref() == "any")
|
|
77
78
|
}
|
|
78
79
|
fn is_named(ann: &TypeAnnotation, n: &str) -> bool {
|
|
79
|
-
matches!(ann, TypeAnnotation::Simple(s) if s.as_ref() == n)
|
|
80
|
+
matches!(ann, TypeAnnotation::Simple(s, _) if s.as_ref() == n)
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
/// Display a type for diagnostics (close to the source syntax).
|
|
83
84
|
fn show(ann: &TypeAnnotation) -> String {
|
|
84
85
|
match ann {
|
|
85
|
-
TypeAnnotation::Simple(s) => s.to_string(),
|
|
86
|
+
TypeAnnotation::Simple(s, _) => s.to_string(),
|
|
86
87
|
TypeAnnotation::Array(t) => format!("{}[]", show(t)),
|
|
87
88
|
TypeAnnotation::Object(fs) => {
|
|
88
89
|
let inner: Vec<String> = fs.iter().map(|(k, t)| format!("{}: {}", k, show(t))).collect();
|
|
@@ -116,7 +117,7 @@ fn resolve<'a>(
|
|
|
116
117
|
if depth > 8 {
|
|
117
118
|
return ann;
|
|
118
119
|
}
|
|
119
|
-
if let TypeAnnotation::Simple(s) = ann {
|
|
120
|
+
if let TypeAnnotation::Simple(s, _) = ann {
|
|
120
121
|
if let Some(t) = aliases.get(s.as_ref()) {
|
|
121
122
|
return resolve(t, aliases, depth + 1);
|
|
122
123
|
}
|
|
@@ -138,12 +139,12 @@ fn assignable(
|
|
|
138
139
|
}
|
|
139
140
|
// `null`/`void`/`undefined` are leniently compatible (tish uses `null` for optionals; checking
|
|
140
141
|
// it strictly would false-positive without real union/optional support).
|
|
141
|
-
if matches!(a, TypeAnnotation::Simple(s) if matches!(s.as_ref(), "null" | "void" | "undefined")) {
|
|
142
|
+
if matches!(a, TypeAnnotation::Simple(s, _) if matches!(s.as_ref(), "null" | "void" | "undefined")) {
|
|
142
143
|
return true;
|
|
143
144
|
}
|
|
144
145
|
use TypeAnnotation::*;
|
|
145
146
|
match (a, e) {
|
|
146
|
-
(Simple(x), Simple(y)) => {
|
|
147
|
+
(Simple(x, _), Simple(y, _)) => {
|
|
147
148
|
// Strict only among the three scalar primitives; any user-defined / unresolved name is
|
|
148
149
|
// treated as compatible.
|
|
149
150
|
let strict = |s: &str| matches!(s, "number" | "string" | "boolean");
|
|
@@ -155,7 +156,7 @@ fn assignable(
|
|
|
155
156
|
}
|
|
156
157
|
(Array(ax), Array(ey)) => assignable(ax, ey, aliases),
|
|
157
158
|
// array vs non-array (after alias/any resolution) is a clear mismatch
|
|
158
|
-
(Array(_), Simple(_)) | (Simple(_), Array(_)) => false,
|
|
159
|
+
(Array(_), Simple(_, _)) | (Simple(_, _), Array(_)) => false,
|
|
159
160
|
(Object(af), Object(ef)) => ef.iter().all(|(k, et)| {
|
|
160
161
|
af.iter()
|
|
161
162
|
.find(|(ak, _)| ak.as_ref() == k.as_ref())
|
|
@@ -510,7 +511,7 @@ impl CheckCtx {
|
|
|
510
511
|
TypeAnnotation::Array(_) if name.as_ref() == "length" => {
|
|
511
512
|
return Some(simple("number"));
|
|
512
513
|
}
|
|
513
|
-
TypeAnnotation::Simple(s)
|
|
514
|
+
TypeAnnotation::Simple(s, _)
|
|
514
515
|
if s.as_ref() == "string" && name.as_ref() == "length" =>
|
|
515
516
|
{
|
|
516
517
|
return Some(simple("number"));
|
|
@@ -589,7 +590,7 @@ impl CheckCtx {
|
|
|
589
590
|
let mut fields = Vec::new();
|
|
590
591
|
for p in props {
|
|
591
592
|
match p {
|
|
592
|
-
ObjectProp::KeyValue(k, v) => {
|
|
593
|
+
ObjectProp::KeyValue(k, v, _) => {
|
|
593
594
|
let t = self.synth(v)?;
|
|
594
595
|
fields.push((k.clone(), t));
|
|
595
596
|
}
|