@tishlang/tish 1.0.33 → 1.0.34
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/crates/tish/Cargo.toml +1 -1
- package/crates/tish_compile/src/codegen.rs +371 -35
- package/crates/tish_compile/src/infer.rs +236 -0
- package/crates/tish_compile/src/lib.rs +19 -7
- package/crates/tish_compile/src/types.rs +42 -5
- package/crates/tish_core/src/value.rs +3 -0
- package/crates/tish_cranelift/src/lib.rs +6 -4
- package/crates/tish_cranelift/src/lower.rs +5 -3
- package/crates/tish_cranelift_runtime/src/lib.rs +4 -2
- package/crates/tish_eval/src/eval.rs +23 -7
- package/crates/tish_eval/src/lib.rs +7 -0
- package/crates/tish_llvm/src/lib.rs +4 -4
- package/crates/tish_native/src/lib.rs +10 -11
- package/crates/tish_parser/src/parser.rs +7 -1
- package/crates/tish_runtime/src/http_fetch.rs +8 -0
- 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
package/crates/tish/Cargo.toml
CHANGED
|
@@ -443,6 +443,9 @@ pub fn compile_with_native_modules(
|
|
|
443
443
|
optimize: bool,
|
|
444
444
|
) -> Result<String, CompileError> {
|
|
445
445
|
let program = if optimize { tishlang_opt::optimize(program) } else { program.clone() };
|
|
446
|
+
// Type-inference pass: fills in `type_ann` on unannotated VarDecl nodes where
|
|
447
|
+
// the type is unambiguous (literals, arithmetic of typed vars, etc.).
|
|
448
|
+
let program = crate::infer::infer_program(&program);
|
|
446
449
|
let map: std::collections::HashMap<String, (String, String)> = native_modules
|
|
447
450
|
.iter()
|
|
448
451
|
.map(|m| (m.spec.clone(), (m.crate_name.clone(), m.export_fn.clone())))
|
|
@@ -706,7 +709,18 @@ impl Codegen {
|
|
|
706
709
|
fn emit_inc_dec(&self, name: &str, is_prefix: bool, delta: &str, op_name: &str) -> String {
|
|
707
710
|
let n = Self::escape_ident(name);
|
|
708
711
|
let is_wrapped = self.refcell_wrapped_vars.contains(name);
|
|
709
|
-
|
|
712
|
+
let var_type = self.type_context.get_type(name);
|
|
713
|
+
|
|
714
|
+
// Native fast path: f64 variable → avoid boxing/unboxing.
|
|
715
|
+
if !is_wrapped && var_type == RustType::F64 {
|
|
716
|
+
let op_assign = if delta.contains('+') { "+=" } else { "-=" };
|
|
717
|
+
return if is_prefix {
|
|
718
|
+
format!("{{ {n} {op_assign} 1.0_f64; Value::Number({n}) }}")
|
|
719
|
+
} else {
|
|
720
|
+
format!("{{ let _prev = {n}; {n} {op_assign} 1.0_f64; Value::Number(_prev) }}")
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
710
724
|
if is_prefix {
|
|
711
725
|
if is_wrapped {
|
|
712
726
|
format!(
|
|
@@ -717,16 +731,14 @@ impl Codegen {
|
|
|
717
731
|
"{{ {n} = Value::Number(match &{n} {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); {n}.clone() }}"
|
|
718
732
|
)
|
|
719
733
|
}
|
|
734
|
+
} else if is_wrapped {
|
|
735
|
+
format!(
|
|
736
|
+
"{{ let _v = (*{n}.borrow()).clone(); *{n}.borrow_mut() = Value::Number(match &_v {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); _v }}"
|
|
737
|
+
)
|
|
720
738
|
} else {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
)
|
|
725
|
-
} else {
|
|
726
|
-
format!(
|
|
727
|
-
"{{ let _v = {n}.clone(); {n} = Value::Number(match &_v {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); _v }}"
|
|
728
|
-
)
|
|
729
|
-
}
|
|
739
|
+
format!(
|
|
740
|
+
"{{ let _v = {n}.clone(); {n} = Value::Number(match &_v {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); _v }}"
|
|
741
|
+
)
|
|
730
742
|
}
|
|
731
743
|
}
|
|
732
744
|
|
|
@@ -1133,8 +1145,8 @@ impl Codegen {
|
|
|
1133
1145
|
else_branch,
|
|
1134
1146
|
..
|
|
1135
1147
|
} => {
|
|
1136
|
-
let c = self.
|
|
1137
|
-
self.write(&format!("if {}
|
|
1148
|
+
let c = self.emit_cond_expr(cond)?;
|
|
1149
|
+
self.write(&format!("if {} {{\n", c));
|
|
1138
1150
|
self.indent += 1;
|
|
1139
1151
|
self.emit_statement(then_branch)?;
|
|
1140
1152
|
self.indent -= 1;
|
|
@@ -1147,11 +1159,11 @@ impl Codegen {
|
|
|
1147
1159
|
self.writeln("}");
|
|
1148
1160
|
}
|
|
1149
1161
|
Statement::While { cond, body, .. } => {
|
|
1150
|
-
let c = self.
|
|
1162
|
+
let c = self.emit_cond_expr(cond)?;
|
|
1151
1163
|
let label = format!("'while_loop_{}", self.loop_label_index);
|
|
1152
1164
|
self.loop_label_index += 1;
|
|
1153
1165
|
self.loop_stack.push((label.clone(), None));
|
|
1154
|
-
self.write(&format!("{}: while {}
|
|
1166
|
+
self.write(&format!("{}: while {} {{\n", label, c));
|
|
1155
1167
|
self.indent += 1;
|
|
1156
1168
|
self.emit_statement(body)?;
|
|
1157
1169
|
self.loop_stack.pop();
|
|
@@ -1209,7 +1221,7 @@ impl Codegen {
|
|
|
1209
1221
|
self.loop_label_index += 1;
|
|
1210
1222
|
let cond_expr = cond
|
|
1211
1223
|
.as_ref()
|
|
1212
|
-
.map(|c|
|
|
1224
|
+
.map(|c| self.emit_cond_expr(c).unwrap())
|
|
1213
1225
|
.unwrap_or_else(|| "true".to_string());
|
|
1214
1226
|
let update_code = update.as_ref().map(|u| {
|
|
1215
1227
|
let ue = self.emit_expr(u).unwrap();
|
|
@@ -1301,14 +1313,14 @@ impl Codegen {
|
|
|
1301
1313
|
self.writeln("}");
|
|
1302
1314
|
}
|
|
1303
1315
|
Statement::DoWhile { body, cond, .. } => {
|
|
1304
|
-
let c = self.
|
|
1316
|
+
let c = self.emit_cond_expr(cond)?;
|
|
1305
1317
|
let label = format!("'dowhile_loop_{}", self.loop_label_index);
|
|
1306
1318
|
self.loop_label_index += 1;
|
|
1307
1319
|
self.loop_stack.push((label.clone(), None));
|
|
1308
1320
|
self.write(&format!("{}: loop {{\n", label));
|
|
1309
1321
|
self.indent += 1;
|
|
1310
1322
|
self.emit_statement(body)?;
|
|
1311
|
-
self.write(&format!("if !{}
|
|
1323
|
+
self.write(&format!("if !{} {{ break; }}\n", c));
|
|
1312
1324
|
self.loop_stack.pop();
|
|
1313
1325
|
self.indent -= 1;
|
|
1314
1326
|
self.writeln("}");
|
|
@@ -1715,10 +1727,10 @@ impl Codegen {
|
|
|
1715
1727
|
}
|
|
1716
1728
|
}
|
|
1717
1729
|
}
|
|
1718
|
-
Expr::Binary {
|
|
1719
|
-
|
|
1720
|
-
let
|
|
1721
|
-
|
|
1730
|
+
Expr::Binary { .. } => {
|
|
1731
|
+
// Delegate to emit_typed_expr; wrap the native result in Value.
|
|
1732
|
+
let (code, ty) = self.emit_typed_expr(expr)?;
|
|
1733
|
+
if ty.is_native() { ty.to_value_expr(&code) } else { code }
|
|
1722
1734
|
}
|
|
1723
1735
|
Expr::Unary { op, operand, .. } => {
|
|
1724
1736
|
let o = self.emit_expr(operand)?;
|
|
@@ -1779,6 +1791,37 @@ impl Codegen {
|
|
|
1779
1791
|
|
|
1780
1792
|
// Check for built-in method calls on arrays/strings
|
|
1781
1793
|
if let Expr::Member { object, prop: MemberProp::Name(method_name), .. } = callee.as_ref() {
|
|
1794
|
+
// ── native Vec<T> push fast path ──────────────────────────────
|
|
1795
|
+
if method_name.as_ref() == "push" {
|
|
1796
|
+
if let Expr::Ident { name, .. } = object.as_ref() {
|
|
1797
|
+
if !self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
1798
|
+
let obj_type = self.type_context.get_type(name.as_ref());
|
|
1799
|
+
if let RustType::Vec(elem_type) = obj_type {
|
|
1800
|
+
let esc_obj = Self::escape_ident(name.as_ref()).into_owned();
|
|
1801
|
+
// Collect push arguments as native values.
|
|
1802
|
+
let mut push_stmts: Vec<String> = Vec::new();
|
|
1803
|
+
for a in args {
|
|
1804
|
+
if let CallArg::Expr(e) = a {
|
|
1805
|
+
let (val_code, val_ty) = self.emit_typed_expr(e)?;
|
|
1806
|
+
let native_val = if val_ty == *elem_type {
|
|
1807
|
+
val_code
|
|
1808
|
+
} else if val_ty == RustType::Value {
|
|
1809
|
+
elem_type.from_value_expr(&val_code)
|
|
1810
|
+
} else {
|
|
1811
|
+
val_code
|
|
1812
|
+
};
|
|
1813
|
+
push_stmts.push(format!("{}.push({});", esc_obj, native_val));
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
return Ok(format!(
|
|
1817
|
+
"{{ {} Value::Null }}",
|
|
1818
|
+
push_stmts.join(" ")
|
|
1819
|
+
));
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1782
1825
|
let obj_expr = self.emit_expr(object)?;
|
|
1783
1826
|
let arg_exprs: Result<Vec<_>, _> =
|
|
1784
1827
|
args.iter().map(|a| self.emit_call_arg(a)).collect();
|
|
@@ -2136,23 +2179,24 @@ impl Codegen {
|
|
|
2136
2179
|
format!("tishlang_runtime::get_prop(&{}, {})", obj, key)
|
|
2137
2180
|
}
|
|
2138
2181
|
}
|
|
2182
|
+
Expr::Index { optional, .. } if !optional => {
|
|
2183
|
+
// Try native Vec<T> fast path via emit_typed_expr; wrap result.
|
|
2184
|
+
let (code, ty) = self.emit_typed_expr(expr)?;
|
|
2185
|
+
if ty.is_native() { ty.to_value_expr(&code) } else { code }
|
|
2186
|
+
}
|
|
2139
2187
|
Expr::Index {
|
|
2140
2188
|
object,
|
|
2141
2189
|
index,
|
|
2142
|
-
optional,
|
|
2143
2190
|
..
|
|
2144
2191
|
} => {
|
|
2192
|
+
// optional chaining: always use runtime path
|
|
2145
2193
|
let obj = self.emit_expr(object)?;
|
|
2146
2194
|
let idx = self.emit_expr(index)?;
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
)
|
|
2153
|
-
} else {
|
|
2154
|
-
format!("tishlang_runtime::get_index(&{}, &{})", obj, idx)
|
|
2155
|
-
}
|
|
2195
|
+
format!(
|
|
2196
|
+
"{{ let o = {}.clone(); if matches!(o, Value::Null) {{ Value::Null }} else {{ \
|
|
2197
|
+
tishlang_runtime::get_index(&o, &{}) }} }}",
|
|
2198
|
+
obj, idx
|
|
2199
|
+
)
|
|
2156
2200
|
}
|
|
2157
2201
|
Expr::Conditional {
|
|
2158
2202
|
cond,
|
|
@@ -2257,8 +2301,29 @@ impl Codegen {
|
|
|
2257
2301
|
}
|
|
2258
2302
|
}
|
|
2259
2303
|
Expr::Assign { name, value, .. } => {
|
|
2260
|
-
let val = self.emit_expr(value)?;
|
|
2261
2304
|
let escaped = Self::escape_ident(name.as_ref());
|
|
2305
|
+
// Native fast path: if the target is a scalar native type, emit
|
|
2306
|
+
// a direct assignment without boxing/unboxing through Value.
|
|
2307
|
+
if !self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
2308
|
+
let rust_type = self.type_context.get_type(name.as_ref());
|
|
2309
|
+
if rust_type.is_native() && matches!(rust_type, RustType::F64 | RustType::Bool | RustType::String) {
|
|
2310
|
+
let (val_code, val_ty) = self.emit_typed_expr(value)?;
|
|
2311
|
+
let native_val = if val_ty == rust_type {
|
|
2312
|
+
val_code
|
|
2313
|
+
} else if val_ty == RustType::Value {
|
|
2314
|
+
rust_type.from_value_expr(&val_code)
|
|
2315
|
+
} else {
|
|
2316
|
+
val_code
|
|
2317
|
+
};
|
|
2318
|
+
let return_val = rust_type.to_value_expr(&escaped);
|
|
2319
|
+
return Ok(format!(
|
|
2320
|
+
"{{ {} = {}; {} }}",
|
|
2321
|
+
escaped, native_val, return_val
|
|
2322
|
+
));
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
// Fallback: Value path
|
|
2326
|
+
let val = self.emit_expr(value)?;
|
|
2262
2327
|
let needs_outer_clone = self.should_clone(value);
|
|
2263
2328
|
if self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
2264
2329
|
if needs_outer_clone {
|
|
@@ -2267,7 +2332,6 @@ impl Codegen {
|
|
|
2267
2332
|
format!("{{ let _v = {}; *{}.borrow_mut() = _v.clone(); _v }}", val, escaped)
|
|
2268
2333
|
}
|
|
2269
2334
|
} else {
|
|
2270
|
-
// Use type_context: typed vars need from_value_expr; Value needs .clone() (we return _v)
|
|
2271
2335
|
let rust_type = self.type_context.get_type(name.as_ref());
|
|
2272
2336
|
let assign_rhs = if matches!(rust_type, RustType::Value) {
|
|
2273
2337
|
"_v.clone()".to_string()
|
|
@@ -2326,8 +2390,65 @@ impl Codegen {
|
|
|
2326
2390
|
Expr::PrefixInc { name, .. } => self.emit_inc_dec(name.as_ref(), true, "+ 1.0", "++"),
|
|
2327
2391
|
Expr::PrefixDec { name, .. } => self.emit_inc_dec(name.as_ref(), true, "- 1.0", "--"),
|
|
2328
2392
|
Expr::CompoundAssign { name, op, value, .. } => {
|
|
2329
|
-
let val = self.emit_expr(value)?;
|
|
2330
2393
|
let n = Self::escape_ident(name.as_ref());
|
|
2394
|
+
let is_refcell = self.refcell_wrapped_vars.contains(name.as_ref());
|
|
2395
|
+
let var_type = self.type_context.get_type(name.as_ref());
|
|
2396
|
+
|
|
2397
|
+
// ── native f64 fast path: direct arithmetic operators ─────────
|
|
2398
|
+
// emit_expr must return a Value expression; wrap the result back.
|
|
2399
|
+
if !is_refcell && var_type == RustType::F64 {
|
|
2400
|
+
let (rhs_code, rhs_ty) = self.emit_typed_expr(value)?;
|
|
2401
|
+
let rhs_f64 = if rhs_ty == RustType::F64 {
|
|
2402
|
+
rhs_code
|
|
2403
|
+
} else {
|
|
2404
|
+
// rhs is Value or another native: unbox to f64
|
|
2405
|
+
let rhs_val = if rhs_ty.is_native() {
|
|
2406
|
+
rhs_ty.to_value_expr(&rhs_code)
|
|
2407
|
+
} else {
|
|
2408
|
+
rhs_code
|
|
2409
|
+
};
|
|
2410
|
+
format!("(match &({}) {{ Value::Number(n) => *n, v => panic!(\"compound assign: expected number, got {{:?}}\", v) }})", rhs_val)
|
|
2411
|
+
};
|
|
2412
|
+
let op_str = match op {
|
|
2413
|
+
CompoundOp::Add => "+=",
|
|
2414
|
+
CompoundOp::Sub => "-=",
|
|
2415
|
+
CompoundOp::Mul => "*=",
|
|
2416
|
+
CompoundOp::Div => "/=",
|
|
2417
|
+
CompoundOp::Mod => "%=",
|
|
2418
|
+
};
|
|
2419
|
+
// Wrap in Value::Number so the expression is a valid Value
|
|
2420
|
+
return Ok(format!("{{ {} {} {}; Value::Number({}) }}", n, op_str, rhs_f64, n));
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
// ── native String += fast path: push_str ─────────────────────
|
|
2424
|
+
if !is_refcell && var_type == RustType::String && matches!(op, CompoundOp::Add) {
|
|
2425
|
+
let (rhs_code, rhs_ty) = self.emit_typed_expr(value)?;
|
|
2426
|
+
let rhs_str = if rhs_ty == RustType::String {
|
|
2427
|
+
rhs_code
|
|
2428
|
+
} else {
|
|
2429
|
+
// Convert rhs Value to display string inline
|
|
2430
|
+
let rhs_val = if rhs_ty.is_native() {
|
|
2431
|
+
rhs_ty.to_value_expr(&rhs_code)
|
|
2432
|
+
} else {
|
|
2433
|
+
rhs_code
|
|
2434
|
+
};
|
|
2435
|
+
format!(
|
|
2436
|
+
"match &({}) {{ \
|
|
2437
|
+
Value::String(s) => s.to_string(), \
|
|
2438
|
+
Value::Number(n) => {{ let i = *n as i64; if (*n - i as f64).abs() < f64::EPSILON {{ i.to_string() }} else {{ n.to_string() }} }}, \
|
|
2439
|
+
Value::Bool(b) => b.to_string(), \
|
|
2440
|
+
Value::Null => \"null\".to_string(), \
|
|
2441
|
+
other => format!(\"{{:?}}\", other) }}",
|
|
2442
|
+
rhs_val
|
|
2443
|
+
)
|
|
2444
|
+
};
|
|
2445
|
+
// Wrap in Value::String so the expression is a valid Value
|
|
2446
|
+
return Ok(format!("{{ {}.push_str(&({})); Value::String({}.clone().into()) }}", n, rhs_str, n));
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// ── fallback: Value path ──────────────────────────────────────
|
|
2450
|
+
// If the variable is native, wrap it as Value before calling ops::
|
|
2451
|
+
let val = self.emit_expr(value)?;
|
|
2331
2452
|
let op_fn = match op {
|
|
2332
2453
|
CompoundOp::Add => "add",
|
|
2333
2454
|
CompoundOp::Sub => "sub",
|
|
@@ -2335,11 +2456,20 @@ impl Codegen {
|
|
|
2335
2456
|
CompoundOp::Div => "div",
|
|
2336
2457
|
CompoundOp::Mod => "modulo",
|
|
2337
2458
|
};
|
|
2338
|
-
if
|
|
2459
|
+
if is_refcell {
|
|
2339
2460
|
format!(
|
|
2340
2461
|
"{{ let _rhs = ({}).clone(); *{}.borrow_mut() = tishlang_runtime::ops::{}(&*{}.borrow(), &_rhs)?; (*{}.borrow()).clone() }}",
|
|
2341
2462
|
val, n, op_fn, n, n
|
|
2342
2463
|
)
|
|
2464
|
+
} else if var_type.is_native() {
|
|
2465
|
+
// Wrap native lhs as Value, run ops::, unbox result back to native
|
|
2466
|
+
let n_as_value = var_type.to_value_expr(&n);
|
|
2467
|
+
let result_native = var_type.from_value_expr("_result");
|
|
2468
|
+
let n_as_value2 = var_type.to_value_expr(&n);
|
|
2469
|
+
format!(
|
|
2470
|
+
"{{ let _lhs = {}; let _rhs = ({}).clone(); let _result = tishlang_runtime::ops::{}(&_lhs, &_rhs)?; {} = {}; {} }}",
|
|
2471
|
+
n_as_value, val, op_fn, n, result_native, n_as_value2
|
|
2472
|
+
)
|
|
2343
2473
|
} else {
|
|
2344
2474
|
format!(
|
|
2345
2475
|
"{{ let _rhs = ({}).clone(); {} = tishlang_runtime::ops::{}(&{}, &_rhs)?; {}.clone() }}",
|
|
@@ -2351,6 +2481,35 @@ impl Codegen {
|
|
|
2351
2481
|
let val = self.emit_expr(value)?;
|
|
2352
2482
|
let n = Self::escape_ident(name.as_ref()).into_owned();
|
|
2353
2483
|
let is_refcell = self.refcell_wrapped_vars.contains(name.as_ref());
|
|
2484
|
+
let var_type = self.type_context.get_type(name.as_ref());
|
|
2485
|
+
|
|
2486
|
+
// ── native type: wrap for condition, unbox for assignment ──────
|
|
2487
|
+
if !is_refcell && var_type.is_native() {
|
|
2488
|
+
// n_as_value uses .clone() for String so we don't consume n
|
|
2489
|
+
let n_as_value = var_type.to_value_expr(&n);
|
|
2490
|
+
let val_as_native = var_type.from_value_expr("_v");
|
|
2491
|
+
let (cond, assign_and_return, else_expr) = match op {
|
|
2492
|
+
LogicalAssignOp::AndAnd => (
|
|
2493
|
+
format!("{{ let __chk = {}; __chk.is_truthy() }}", n_as_value),
|
|
2494
|
+
format!("{{ let _v = ({}).clone(); {} = {}; {} }}", val, n, val_as_native, var_type.to_value_expr(&n)),
|
|
2495
|
+
var_type.to_value_expr(&n),
|
|
2496
|
+
),
|
|
2497
|
+
LogicalAssignOp::OrOr => (
|
|
2498
|
+
format!("!{{ let __chk = {}; __chk.is_truthy() }}", n_as_value),
|
|
2499
|
+
format!("{{ let _v = ({}).clone(); {} = {}; {} }}", val, n, val_as_native, var_type.to_value_expr(&n)),
|
|
2500
|
+
var_type.to_value_expr(&n),
|
|
2501
|
+
),
|
|
2502
|
+
// Native types (f64, String, bool) are never null — ??= is a no-op
|
|
2503
|
+
LogicalAssignOp::Nullish => (
|
|
2504
|
+
"false".to_string(),
|
|
2505
|
+
var_type.to_value_expr(&n), // unreachable but must type-check
|
|
2506
|
+
var_type.to_value_expr(&n),
|
|
2507
|
+
),
|
|
2508
|
+
};
|
|
2509
|
+
return Ok(format!("{{ if {} {{ {} }} else {{ {} }} }}", cond, assign_and_return, else_expr));
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
// ── Value / refcell path ──────────────────────────────────────
|
|
2354
2513
|
let (cond, assign_and_return, else_expr) = if is_refcell {
|
|
2355
2514
|
match op {
|
|
2356
2515
|
LogicalAssignOp::AndAnd => (
|
|
@@ -2401,6 +2560,43 @@ impl Codegen {
|
|
|
2401
2560
|
)
|
|
2402
2561
|
}
|
|
2403
2562
|
Expr::IndexAssign { object, index, value, .. } => {
|
|
2563
|
+
// Native fast path: Vec<T>[i] = v
|
|
2564
|
+
if let Expr::Ident { name, .. } = object.as_ref() {
|
|
2565
|
+
if !self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
2566
|
+
let obj_type = self.type_context.get_type(name.as_ref());
|
|
2567
|
+
if let RustType::Vec(elem_type) = obj_type {
|
|
2568
|
+
let esc_obj = Self::escape_ident(name.as_ref()).into_owned();
|
|
2569
|
+
let (idx_code, idx_ty) = self.emit_typed_expr(index)?;
|
|
2570
|
+
let idx_usize = if idx_ty == RustType::F64 {
|
|
2571
|
+
format!("({}) as usize", idx_code)
|
|
2572
|
+
} else {
|
|
2573
|
+
let iv = if idx_ty.is_native() {
|
|
2574
|
+
idx_ty.to_value_expr(&idx_code)
|
|
2575
|
+
} else {
|
|
2576
|
+
idx_code
|
|
2577
|
+
};
|
|
2578
|
+
format!(
|
|
2579
|
+
"{{ let _i = &{}; if let Value::Number(n) = _i {{ *n as usize }} else {{ panic!(\"array index must be a number\") }} }}",
|
|
2580
|
+
iv
|
|
2581
|
+
)
|
|
2582
|
+
};
|
|
2583
|
+
let (val_code, val_ty) = self.emit_typed_expr(value)?;
|
|
2584
|
+
let native_val = if val_ty == *elem_type {
|
|
2585
|
+
val_code
|
|
2586
|
+
} else if val_ty == RustType::Value {
|
|
2587
|
+
elem_type.from_value_expr(&val_code)
|
|
2588
|
+
} else {
|
|
2589
|
+
// both native but different type — best effort
|
|
2590
|
+
val_code
|
|
2591
|
+
};
|
|
2592
|
+
return Ok(format!(
|
|
2593
|
+
"{{ {}[{}] = {}; Value::Null }}",
|
|
2594
|
+
esc_obj, idx_usize, native_val
|
|
2595
|
+
));
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
// Fallback: runtime set_index
|
|
2404
2600
|
let obj = self.emit_expr(object)?;
|
|
2405
2601
|
let idx = self.emit_expr(index)?;
|
|
2406
2602
|
let val = self.emit_expr(value)?;
|
|
@@ -3438,6 +3634,146 @@ impl Codegen {
|
|
|
3438
3634
|
Ok(target_type.from_value_expr(&value_expr))
|
|
3439
3635
|
}
|
|
3440
3636
|
|
|
3637
|
+
/// Emit an expression and return `(code, type)`.
|
|
3638
|
+
///
|
|
3639
|
+
/// When `type` is a native type (`F64`, `Bool`, `String`, `Vec<T>`, …), `code`
|
|
3640
|
+
/// evaluates to a Rust value of that type directly — **not** a `Value`.
|
|
3641
|
+
/// When `type` is `RustType::Value`, `code` evaluates to a `Value`.
|
|
3642
|
+
///
|
|
3643
|
+
/// This is the fast-path used by callers that want to propagate native types
|
|
3644
|
+
/// through arithmetic, indexing, and assignments. For any expression this
|
|
3645
|
+
/// function cannot handle natively, it falls back to `emit_expr` and returns
|
|
3646
|
+
/// `RustType::Value`.
|
|
3647
|
+
fn emit_typed_expr(&mut self, expr: &Expr) -> Result<(String, RustType), CompileError> {
|
|
3648
|
+
match expr {
|
|
3649
|
+
// ── literals ─────────────────────────────────────────────────────────
|
|
3650
|
+
Expr::Literal { value, .. } => match value {
|
|
3651
|
+
Literal::Number(n) => Ok((format!("{}_f64", n), RustType::F64)),
|
|
3652
|
+
Literal::String(s) => Ok((format!("{:?}.to_string()", s.as_ref()), RustType::String)),
|
|
3653
|
+
Literal::Bool(b) => Ok((format!("{}", b), RustType::Bool)),
|
|
3654
|
+
Literal::Null => Ok(("Value::Null".to_string(), RustType::Value)),
|
|
3655
|
+
},
|
|
3656
|
+
|
|
3657
|
+
// ── identifiers ──────────────────────────────────────────────────────
|
|
3658
|
+
Expr::Ident { name, .. } => {
|
|
3659
|
+
let escaped = Self::escape_ident(name.as_ref());
|
|
3660
|
+
if self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
3661
|
+
// RefCell-wrapped: unwrap via borrow and return Value
|
|
3662
|
+
Ok((format!("(*{}.borrow()).clone()", escaped), RustType::Value))
|
|
3663
|
+
} else {
|
|
3664
|
+
let var_type = self.type_context.get_type(name.as_ref());
|
|
3665
|
+
if var_type.is_native() {
|
|
3666
|
+
Ok((escaped.into_owned(), var_type))
|
|
3667
|
+
} else {
|
|
3668
|
+
Ok((escaped.into_owned(), RustType::Value))
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
// ── binary expressions ───────────────────────────────────────────────
|
|
3674
|
+
Expr::Binary { left, op, right, span, .. } => {
|
|
3675
|
+
let (l, lt) = self.emit_typed_expr(left)?;
|
|
3676
|
+
let (r, rt) = self.emit_typed_expr(right)?;
|
|
3677
|
+
|
|
3678
|
+
if let Some(result_ty) = RustType::result_type_of_binop(*op, <, &rt) {
|
|
3679
|
+
// Both sides are compatible native types → emit native op.
|
|
3680
|
+
let code = match op {
|
|
3681
|
+
BinOp::Add => format!("({} + {})", l, r),
|
|
3682
|
+
BinOp::Sub => format!("({} - {})", l, r),
|
|
3683
|
+
BinOp::Mul => format!("({} * {})", l, r),
|
|
3684
|
+
BinOp::Div => format!("({} / {})", l, r),
|
|
3685
|
+
BinOp::Mod => format!("({} % {})", l, r),
|
|
3686
|
+
BinOp::Pow => format!("({}).powf({})", l, r),
|
|
3687
|
+
BinOp::Lt => format!("({} < {})", l, r),
|
|
3688
|
+
BinOp::Le => format!("({} <= {})", l, r),
|
|
3689
|
+
BinOp::Gt => format!("({} > {})", l, r),
|
|
3690
|
+
BinOp::Ge => format!("({} >= {})", l, r),
|
|
3691
|
+
BinOp::StrictEq => format!("({} == {})", l, r),
|
|
3692
|
+
BinOp::StrictNe => format!("({} != {})", l, r),
|
|
3693
|
+
BinOp::And => format!("({} && {})", l, r),
|
|
3694
|
+
BinOp::Or => format!("({} || {})", l, r),
|
|
3695
|
+
_ => unreachable!("result_type_of_binop covers all handled ops"),
|
|
3696
|
+
};
|
|
3697
|
+
return Ok((code, result_ty));
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
// Fall back: convert both sides to Value and use the runtime.
|
|
3701
|
+
let lv = if lt.is_native() { lt.to_value_expr(&l) } else { l };
|
|
3702
|
+
let rv = if rt.is_native() { rt.to_value_expr(&r) } else { r };
|
|
3703
|
+
let result = self.emit_binop(&lv, *op, &rv, *span)?;
|
|
3704
|
+
Ok((result, RustType::Value))
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
// ── array indexing ───────────────────────────────────────────────────
|
|
3708
|
+
Expr::Index { object, index, optional, .. } => {
|
|
3709
|
+
// Native fast path: `vec[i]` where vec is Vec<T> and i is numeric.
|
|
3710
|
+
if !optional {
|
|
3711
|
+
if let Expr::Ident { name, .. } = object.as_ref() {
|
|
3712
|
+
if !self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
3713
|
+
let obj_type = self.type_context.get_type(name.as_ref());
|
|
3714
|
+
if let RustType::Vec(elem_type) = &obj_type {
|
|
3715
|
+
let esc_obj = Self::escape_ident(name.as_ref()).into_owned();
|
|
3716
|
+
let (idx_code, idx_ty) = self.emit_typed_expr(index)?;
|
|
3717
|
+
let idx_usize = if idx_ty == RustType::F64 {
|
|
3718
|
+
format!("({}) as usize", idx_code)
|
|
3719
|
+
} else {
|
|
3720
|
+
let iv = if idx_ty.is_native() {
|
|
3721
|
+
idx_ty.to_value_expr(&idx_code)
|
|
3722
|
+
} else {
|
|
3723
|
+
idx_code
|
|
3724
|
+
};
|
|
3725
|
+
format!(
|
|
3726
|
+
"{{ let _i = &{}; if let Value::Number(n) = _i {{ *n as usize }} else {{ panic!(\"array index must be a number\") }} }}",
|
|
3727
|
+
iv
|
|
3728
|
+
)
|
|
3729
|
+
};
|
|
3730
|
+
let elem_ty = *elem_type.clone();
|
|
3731
|
+
return Ok((format!("{}[{}]", esc_obj, idx_usize), elem_ty));
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
// Value fallback: emit runtime code directly to avoid cycles
|
|
3737
|
+
// (emit_expr for !optional Index delegates here, so we must not call emit_expr(expr))
|
|
3738
|
+
let obj = self.emit_expr(object)?;
|
|
3739
|
+
let idx = self.emit_expr(index)?;
|
|
3740
|
+
let result = if *optional {
|
|
3741
|
+
format!(
|
|
3742
|
+
"{{ let o = {}.clone(); if matches!(o, Value::Null) {{ Value::Null }} else {{ \
|
|
3743
|
+
tishlang_runtime::get_index(&o, &{}) }} }}",
|
|
3744
|
+
obj, idx
|
|
3745
|
+
)
|
|
3746
|
+
} else {
|
|
3747
|
+
format!("tishlang_runtime::get_index(&{}, &{})", obj, idx)
|
|
3748
|
+
};
|
|
3749
|
+
Ok((result, RustType::Value))
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
// ── everything else: delegate to emit_expr ───────────────────────────
|
|
3753
|
+
_ => {
|
|
3754
|
+
let result = self.emit_expr(expr)?;
|
|
3755
|
+
Ok((result, RustType::Value))
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
/// Emit a condition expression as a Rust `bool`.
|
|
3761
|
+
///
|
|
3762
|
+
/// Returns a `bool`-typed Rust expression when the condition can be
|
|
3763
|
+
/// determined to be native (e.g. `i < N` where both are `f64`), otherwise
|
|
3764
|
+
/// falls back to `{value}.is_truthy()`.
|
|
3765
|
+
fn emit_cond_expr(&mut self, expr: &Expr) -> Result<String, CompileError> {
|
|
3766
|
+
let (code, ty) = self.emit_typed_expr(expr)?;
|
|
3767
|
+
if ty == RustType::Bool {
|
|
3768
|
+
Ok(code)
|
|
3769
|
+
} else if ty.is_native() {
|
|
3770
|
+
// Non-bool native type: convert to Value and use is_truthy
|
|
3771
|
+
Ok(format!("{}.is_truthy()", ty.to_value_expr(&code)))
|
|
3772
|
+
} else {
|
|
3773
|
+
Ok(format!("{}.is_truthy()", code))
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3441
3777
|
fn emit_binop(
|
|
3442
3778
|
&self,
|
|
3443
3779
|
l: &str,
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
//! Type inference pass: annotates `VarDecl` nodes with inferred `TypeAnnotation`s
|
|
2
|
+
//! where the user hasn't provided them, enabling codegen to emit native Rust types.
|
|
3
|
+
//!
|
|
4
|
+
//! Rules (conservative — only infer when unambiguous):
|
|
5
|
+
//! - Number literal init → `number`
|
|
6
|
+
//! - String literal init → `string`
|
|
7
|
+
//! - Bool literal init → `boolean`
|
|
8
|
+
//! - Arithmetic of two `number` expressions → `number`
|
|
9
|
+
//! - Comparison of two `number` expressions → `boolean`
|
|
10
|
+
//! - Already-annotated vars are left unchanged.
|
|
11
|
+
|
|
12
|
+
use std::collections::HashMap;
|
|
13
|
+
use tishlang_ast::{
|
|
14
|
+
ArrowBody, BinOp, CallArg, Expr, FunParam, Literal, Program, Statement, TypeAnnotation,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/// Scoped type environment used during inference.
|
|
18
|
+
#[derive(Default)]
|
|
19
|
+
pub struct InferCtx {
|
|
20
|
+
scopes: Vec<HashMap<String, TypeAnnotation>>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl InferCtx {
|
|
24
|
+
pub fn new() -> Self {
|
|
25
|
+
Self { scopes: vec![HashMap::new()] }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn push_scope(&mut self) {
|
|
29
|
+
self.scopes.push(HashMap::new());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn pop_scope(&mut self) {
|
|
33
|
+
self.scopes.pop();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fn define(&mut self, name: &str, ty: TypeAnnotation) {
|
|
37
|
+
if let Some(s) = self.scopes.last_mut() {
|
|
38
|
+
s.insert(name.to_string(), ty);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub fn lookup(&self, name: &str) -> Option<&TypeAnnotation> {
|
|
43
|
+
for s in self.scopes.iter().rev() {
|
|
44
|
+
if let Some(t) = s.get(name) {
|
|
45
|
+
return Some(t);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
None
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fn is_number(ann: &TypeAnnotation) -> bool {
|
|
53
|
+
matches!(ann, TypeAnnotation::Simple(s) if s.as_ref() == "number")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fn number_ann() -> TypeAnnotation {
|
|
57
|
+
TypeAnnotation::Simple("number".into())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn string_ann() -> TypeAnnotation {
|
|
61
|
+
TypeAnnotation::Simple("string".into())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn bool_ann() -> TypeAnnotation {
|
|
65
|
+
TypeAnnotation::Simple("boolean".into())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Infer the `TypeAnnotation` for an expression, if unambiguous.
|
|
69
|
+
pub fn infer_expr_type(expr: &Expr, ctx: &InferCtx) -> Option<TypeAnnotation> {
|
|
70
|
+
match expr {
|
|
71
|
+
Expr::Literal { value, .. } => match value {
|
|
72
|
+
Literal::Number(_) => Some(number_ann()),
|
|
73
|
+
Literal::String(_) => Some(string_ann()),
|
|
74
|
+
Literal::Bool(_) => Some(bool_ann()),
|
|
75
|
+
Literal::Null => None,
|
|
76
|
+
},
|
|
77
|
+
Expr::Ident { name, .. } => ctx.lookup(name.as_ref()).cloned(),
|
|
78
|
+
Expr::Binary { left, op, right, .. } => {
|
|
79
|
+
let lt = infer_expr_type(left, ctx)?;
|
|
80
|
+
let rt = infer_expr_type(right, ctx)?;
|
|
81
|
+
if is_number(<) && is_number(&rt) {
|
|
82
|
+
match op {
|
|
83
|
+
BinOp::Add
|
|
84
|
+
| BinOp::Sub
|
|
85
|
+
| BinOp::Mul
|
|
86
|
+
| BinOp::Div
|
|
87
|
+
| BinOp::Mod
|
|
88
|
+
| BinOp::Pow => Some(number_ann()),
|
|
89
|
+
BinOp::Lt
|
|
90
|
+
| BinOp::Le
|
|
91
|
+
| BinOp::Gt
|
|
92
|
+
| BinOp::Ge
|
|
93
|
+
| BinOp::StrictEq
|
|
94
|
+
| BinOp::StrictNe => Some(bool_ann()),
|
|
95
|
+
_ => None,
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
None
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
Expr::Unary { op, operand, .. } => {
|
|
102
|
+
use tishlang_ast::UnaryOp;
|
|
103
|
+
match op {
|
|
104
|
+
UnaryOp::Neg | UnaryOp::Pos => {
|
|
105
|
+
let t = infer_expr_type(operand, ctx)?;
|
|
106
|
+
if is_number(&t) { Some(number_ann()) } else { None }
|
|
107
|
+
}
|
|
108
|
+
UnaryOp::Not => Some(bool_ann()),
|
|
109
|
+
_ => None,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
_ => None,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Run inference over a program, returning a modified Program with additional
|
|
117
|
+
/// type annotations filled in on `VarDecl` nodes.
|
|
118
|
+
pub fn infer_program(program: &Program) -> Program {
|
|
119
|
+
let mut ctx = InferCtx::new();
|
|
120
|
+
Program { statements: infer_statements(&program.statements, &mut ctx) }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fn infer_statements(stmts: &[Statement], ctx: &mut InferCtx) -> Vec<Statement> {
|
|
124
|
+
stmts.iter().map(|s| infer_statement(s, ctx)).collect()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn infer_statement(stmt: &Statement, ctx: &mut InferCtx) -> Statement {
|
|
128
|
+
match stmt {
|
|
129
|
+
Statement::VarDecl { name, mutable, type_ann, init, span } => {
|
|
130
|
+
// Already annotated — propagate into ctx but don't change the node.
|
|
131
|
+
if let Some(ann) = type_ann {
|
|
132
|
+
ctx.define(name.as_ref(), ann.clone());
|
|
133
|
+
return stmt.clone();
|
|
134
|
+
}
|
|
135
|
+
// Try to infer from init expression.
|
|
136
|
+
let inferred = init.as_ref().and_then(|e| infer_expr_type(e, ctx));
|
|
137
|
+
if let Some(ref ann) = inferred {
|
|
138
|
+
ctx.define(name.as_ref(), ann.clone());
|
|
139
|
+
}
|
|
140
|
+
Statement::VarDecl {
|
|
141
|
+
name: name.clone(),
|
|
142
|
+
mutable: *mutable,
|
|
143
|
+
type_ann: inferred,
|
|
144
|
+
init: init.clone(),
|
|
145
|
+
span: *span,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
Statement::Block { statements, span } => {
|
|
149
|
+
ctx.push_scope();
|
|
150
|
+
let stmts = infer_statements(statements, ctx);
|
|
151
|
+
ctx.pop_scope();
|
|
152
|
+
Statement::Block { statements: stmts, span: *span }
|
|
153
|
+
}
|
|
154
|
+
Statement::For { init, cond, update, body, span } => {
|
|
155
|
+
// Scope for loop variable
|
|
156
|
+
ctx.push_scope();
|
|
157
|
+
let new_init = init.as_ref().map(|i| Box::new(infer_statement(i, ctx)));
|
|
158
|
+
let new_body = Box::new(infer_statement(body, ctx));
|
|
159
|
+
ctx.pop_scope();
|
|
160
|
+
Statement::For {
|
|
161
|
+
init: new_init,
|
|
162
|
+
cond: cond.clone(),
|
|
163
|
+
update: update.clone(),
|
|
164
|
+
body: new_body,
|
|
165
|
+
span: *span,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
Statement::ForOf { name, iterable, body, span } => {
|
|
169
|
+
ctx.push_scope();
|
|
170
|
+
let new_body = Box::new(infer_statement(body, ctx));
|
|
171
|
+
ctx.pop_scope();
|
|
172
|
+
Statement::ForOf {
|
|
173
|
+
name: name.clone(),
|
|
174
|
+
iterable: iterable.clone(),
|
|
175
|
+
body: new_body,
|
|
176
|
+
span: *span,
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
Statement::While { cond, body, span } => {
|
|
180
|
+
ctx.push_scope();
|
|
181
|
+
let new_body = Box::new(infer_statement(body, ctx));
|
|
182
|
+
ctx.pop_scope();
|
|
183
|
+
Statement::While { cond: cond.clone(), body: new_body, span: *span }
|
|
184
|
+
}
|
|
185
|
+
Statement::DoWhile { body, cond, span } => {
|
|
186
|
+
ctx.push_scope();
|
|
187
|
+
let new_body = Box::new(infer_statement(body, ctx));
|
|
188
|
+
ctx.pop_scope();
|
|
189
|
+
Statement::DoWhile { body: new_body, cond: cond.clone(), span: *span }
|
|
190
|
+
}
|
|
191
|
+
Statement::If { cond, then_branch, else_branch, span } => {
|
|
192
|
+
let new_then = Box::new(infer_statement(then_branch, ctx));
|
|
193
|
+
let new_else = else_branch.as_ref().map(|e| Box::new(infer_statement(e, ctx)));
|
|
194
|
+
Statement::If {
|
|
195
|
+
cond: cond.clone(),
|
|
196
|
+
then_branch: new_then,
|
|
197
|
+
else_branch: new_else,
|
|
198
|
+
span: *span,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
Statement::FunDecl { async_, name, params, rest_param, return_type, body, span } => {
|
|
202
|
+
ctx.push_scope();
|
|
203
|
+
for p in params {
|
|
204
|
+
if let FunParam::Simple(tp) = p {
|
|
205
|
+
if let Some(ann) = &tp.type_ann {
|
|
206
|
+
ctx.define(tp.name.as_ref(), ann.clone());
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if let Some(rp) = rest_param {
|
|
211
|
+
if let Some(ann) = &rp.type_ann {
|
|
212
|
+
ctx.define(rp.name.as_ref(), ann.clone());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
let new_body = Box::new(infer_statement(body, ctx));
|
|
216
|
+
ctx.pop_scope();
|
|
217
|
+
Statement::FunDecl {
|
|
218
|
+
async_: *async_,
|
|
219
|
+
name: name.clone(),
|
|
220
|
+
params: params.clone(),
|
|
221
|
+
rest_param: rest_param.clone(),
|
|
222
|
+
return_type: return_type.clone(),
|
|
223
|
+
body: new_body,
|
|
224
|
+
span: *span,
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// For statements with no interesting sub-structure, clone as-is.
|
|
228
|
+
_ => stmt.clone(),
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Suppress unused import warning — CallArg is used indirectly via tishlang_ast.
|
|
233
|
+
#[allow(dead_code)]
|
|
234
|
+
fn _uses_call_arg(_: &CallArg) {}
|
|
235
|
+
#[allow(dead_code)]
|
|
236
|
+
fn _uses_arrow_body(_: &ArrowBody) {}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
//! Emits Rust source that links to tishlang_runtime.
|
|
4
4
|
|
|
5
5
|
mod codegen;
|
|
6
|
+
mod infer;
|
|
6
7
|
mod resolve;
|
|
7
8
|
mod types;
|
|
8
9
|
|
|
@@ -25,6 +26,9 @@ mod tests {
|
|
|
25
26
|
|
|
26
27
|
#[test]
|
|
27
28
|
fn typed_assign_conversion() {
|
|
29
|
+
// With the inference pass and native emit, `total: number = 0` becomes f64.
|
|
30
|
+
// Assignment `total = total + n` (where n comes from ForOf over a Value::Array)
|
|
31
|
+
// emits a native f64 assignment that unboxes the Value result via from_value_expr.
|
|
28
32
|
let src = r#"
|
|
29
33
|
fn sum(...args: number[]): number {
|
|
30
34
|
let total: number = 0
|
|
@@ -34,11 +38,16 @@ fn sum(...args: number[]): number {
|
|
|
34
38
|
"#;
|
|
35
39
|
let program = parse(src).unwrap();
|
|
36
40
|
let rust = compile(&program).unwrap();
|
|
37
|
-
|
|
41
|
+
// total should be declared as f64
|
|
42
|
+
assert!(rust.contains("let mut total: f64"), "expected total: f64");
|
|
43
|
+
// The return value of run() should convert total back to Value
|
|
44
|
+
assert!(rust.contains("Value::Number(total)"), "expected Value::Number(total) wrapping");
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
#[test]
|
|
41
48
|
fn loop_var_decl_clone_outer_var() {
|
|
49
|
+
// With inference, outerVar = 42 gets inferred as f64. f64 is Copy, so no clone is
|
|
50
|
+
// needed — direct assignment is correct. The test verifies compilation succeeds.
|
|
42
51
|
let src = r#"
|
|
43
52
|
let outerVar = 42
|
|
44
53
|
for (let i = 0; i < 5; i = i + 1) {
|
|
@@ -47,10 +56,9 @@ for (let i = 0; i < 5; i = i + 1) {
|
|
|
47
56
|
"#;
|
|
48
57
|
let program = parse(src).unwrap();
|
|
49
58
|
let rust = compile(&program).unwrap();
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
);
|
|
59
|
+
// outerVar and x are f64 (inferred) — Copy assignment, no .clone() needed.
|
|
60
|
+
assert!(rust.contains("let mut outerVar: f64"), "expected outerVar: f64");
|
|
61
|
+
assert!(rust.contains("let mut x: f64"), "expected x: f64");
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
#[test]
|
|
@@ -90,6 +98,9 @@ fn factory() {
|
|
|
90
98
|
|
|
91
99
|
#[test]
|
|
92
100
|
fn loop_var_decl_clone_via_project_full() {
|
|
101
|
+
// With the inference pass, `let outerVar = 42` is inferred as f64 (Copy) — no clone needed.
|
|
102
|
+
// This test verifies the full benchmark_granular project compiles and that outerVar
|
|
103
|
+
// is emitted as the inferred f64 type rather than requiring a Value clone.
|
|
93
104
|
let manifest = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
94
105
|
let bench = manifest.join("../../tests/core/benchmark_granular.tish").canonicalize().unwrap();
|
|
95
106
|
// Use same default features as tish CLI (http, fs, process, regex)
|
|
@@ -98,9 +109,10 @@ fn factory() {
|
|
|
98
109
|
.map(String::from)
|
|
99
110
|
.collect::<Vec<_>>();
|
|
100
111
|
let (rust, _) = compile_project_full(&bench, bench.parent(), &features, true).unwrap();
|
|
112
|
+
// outerVar = 42 is inferred as f64; f64 is Copy so no .clone() is emitted.
|
|
101
113
|
assert!(
|
|
102
|
-
rust.contains("
|
|
103
|
-
"expected outerVar to be
|
|
114
|
+
rust.contains("let mut outerVar: f64"),
|
|
115
|
+
"expected outerVar to be inferred as f64 (Copy, no clone needed)"
|
|
104
116
|
);
|
|
105
117
|
}
|
|
106
118
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
use std::collections::HashMap;
|
|
6
6
|
use std::sync::Arc;
|
|
7
|
-
use tishlang_ast::TypeAnnotation;
|
|
7
|
+
use tishlang_ast::{BinOp, TypeAnnotation};
|
|
8
8
|
|
|
9
9
|
/// Concrete Rust type representation for code generation.
|
|
10
10
|
#[derive(Debug, Clone, PartialEq)]
|
|
@@ -89,6 +89,38 @@ impl RustType {
|
|
|
89
89
|
!matches!(self, RustType::Value)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/// Check if this type is numeric (f64).
|
|
93
|
+
pub fn is_numeric(&self) -> bool {
|
|
94
|
+
matches!(self, RustType::F64)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Infer the result type of a binary operation given the operand types.
|
|
98
|
+
/// Returns `None` if native code cannot be emitted (fall back to Value path).
|
|
99
|
+
pub fn result_type_of_binop(op: BinOp, lhs: &RustType, rhs: &RustType) -> Option<RustType> {
|
|
100
|
+
if lhs == &RustType::F64 && rhs == &RustType::F64 {
|
|
101
|
+
match op {
|
|
102
|
+
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod | BinOp::Pow => {
|
|
103
|
+
Some(RustType::F64)
|
|
104
|
+
}
|
|
105
|
+
BinOp::Lt
|
|
106
|
+
| BinOp::Le
|
|
107
|
+
| BinOp::Gt
|
|
108
|
+
| BinOp::Ge
|
|
109
|
+
| BinOp::StrictEq
|
|
110
|
+
| BinOp::StrictNe => Some(RustType::Bool),
|
|
111
|
+
_ => None,
|
|
112
|
+
}
|
|
113
|
+
} else if lhs == &RustType::Bool && rhs == &RustType::Bool {
|
|
114
|
+
match op {
|
|
115
|
+
BinOp::And | BinOp::Or => Some(RustType::Bool),
|
|
116
|
+
BinOp::StrictEq | BinOp::StrictNe => Some(RustType::Bool),
|
|
117
|
+
_ => None,
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
None
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
92
124
|
/// Get the Rust type string for code generation.
|
|
93
125
|
pub fn to_rust_type_str(&self) -> String {
|
|
94
126
|
match self {
|
|
@@ -169,14 +201,19 @@ impl RustType {
|
|
|
169
201
|
match self {
|
|
170
202
|
RustType::Value => native_expr.to_string(),
|
|
171
203
|
RustType::F64 => format!("Value::Number({})", native_expr),
|
|
172
|
-
RustType::String => format!("Value::String({}.into())", native_expr),
|
|
204
|
+
RustType::String => format!("Value::String({}.clone().into())", native_expr),
|
|
173
205
|
RustType::Bool => format!("Value::Bool({})", native_expr),
|
|
174
206
|
RustType::Unit => "Value::Null".to_string(),
|
|
175
207
|
RustType::Vec(inner) => {
|
|
176
|
-
|
|
208
|
+
// Use iter()/copied()/cloned() to avoid moving the vector.
|
|
209
|
+
let (iter_suffix, val_expr) = match inner.as_ref() {
|
|
210
|
+
RustType::F64 => (".iter().copied()", "Value::Number(v)".to_string()),
|
|
211
|
+
RustType::Bool => (".iter().copied()", "Value::Bool(v)".to_string()),
|
|
212
|
+
_ => (".iter().cloned()", inner.to_value_expr("v")),
|
|
213
|
+
};
|
|
177
214
|
format!(
|
|
178
|
-
"Value::Array(Rc::new(RefCell::new({}.
|
|
179
|
-
native_expr,
|
|
215
|
+
"Value::Array(Rc::new(RefCell::new({}{}.map(|v| {}).collect())))",
|
|
216
|
+
native_expr, iter_suffix, val_expr
|
|
180
217
|
)
|
|
181
218
|
}
|
|
182
219
|
RustType::Option(inner) => {
|
|
@@ -25,6 +25,9 @@ pub trait TishOpaque: Send + Sync {
|
|
|
25
25
|
|
|
26
26
|
/// Get a method by name. Returns a native function if the method exists.
|
|
27
27
|
fn get_method(&self, name: &str) -> Option<NativeFn>;
|
|
28
|
+
|
|
29
|
+
/// For downcasting `Arc<dyn TishOpaque>` in native crates (e.g. Polars → `DataFrame`).
|
|
30
|
+
fn as_any(&self) -> &dyn std::any::Any;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
/// Trait for Promise-like values that can be awaited (block until settled).
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
//!
|
|
1
|
+
//! Standalone native binary: embedded bytecode + VM (Cranelift used as object builder).
|
|
2
2
|
//!
|
|
3
|
-
//!
|
|
3
|
+
//! Produces an executable that runs **`tishlang_vm`** on serialized bytecode embedded in
|
|
4
|
+
//! the binary — not lowering of Tish opcodes to CLIF/machine code (see module docs in
|
|
5
|
+
//! `lower.rs`). For Rust transpile + `tishlang_runtime`, use `--native-backend rust`.
|
|
4
6
|
|
|
5
7
|
mod link;
|
|
6
8
|
mod lower;
|
|
@@ -25,8 +27,8 @@ impl std::fmt::Display for CraneliftError {
|
|
|
25
27
|
|
|
26
28
|
impl std::error::Error for CraneliftError {}
|
|
27
29
|
|
|
28
|
-
///
|
|
29
|
-
/// `features` are passed to tishlang_cranelift_runtime (e.g. fs, process, http
|
|
30
|
+
/// Build a native binary that embeds `chunk` and runs it with the bytecode VM.
|
|
31
|
+
/// `features` are passed to `tishlang_cranelift_runtime` (e.g. fs, process, http).
|
|
30
32
|
pub fn compile_chunk_to_native(
|
|
31
33
|
chunk: &Chunk,
|
|
32
34
|
output_path: &Path,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
//!
|
|
1
|
+
//! Embed serialized bytecode in an object file for the standalone native binary.
|
|
2
2
|
//!
|
|
3
|
-
//!
|
|
4
|
-
//!
|
|
3
|
+
//! **This is not AOT compilation of Tish into Cranelift IR.** The chunk is stored as
|
|
4
|
+
//! read-only data (`tish_chunk_data`, `tish_chunk_len`). The link step produces an
|
|
5
|
+
//! executable that **deserializes the chunk and runs `tishlang_vm`** — same VM as
|
|
6
|
+
//! `tish run --backend vm`. Cranelift is only the object-file emitter for that blob.
|
|
5
7
|
|
|
6
8
|
use std::path::Path;
|
|
7
9
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
//! Runtime
|
|
1
|
+
//! Runtime linked into the `tish compile --native-backend cranelift` executable.
|
|
2
2
|
//!
|
|
3
|
-
//!
|
|
3
|
+
//! **`tish_run_chunk`** deserializes embedded bytecode and runs **`tishlang_vm`** — the same
|
|
4
|
+
//! execution engine as `tish run --backend vm`. The crate name is historical; this is not
|
|
5
|
+
//! running CLIF-emitted machine code for each Tish opcode.
|
|
4
6
|
|
|
5
7
|
use tishlang_bytecode::deserialize;
|
|
6
8
|
use tishlang_vm::Vm;
|
|
@@ -75,6 +75,8 @@ pub struct Evaluator {
|
|
|
75
75
|
module_cache: Rc<RefCell<HashMap<PathBuf, Value>>>,
|
|
76
76
|
/// Directory of the file currently being evaluated (for resolving relative imports)
|
|
77
77
|
current_dir: RefCell<Option<PathBuf>>,
|
|
78
|
+
/// Extra `tish:*` builtins from `TishNativeModule::virtual_builtin_modules` (shared across nested evaluators).
|
|
79
|
+
virtual_builtins: Rc<RefCell<HashMap<Arc<str>, Value>>>,
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
impl Evaluator {
|
|
@@ -173,6 +175,7 @@ impl Evaluator {
|
|
|
173
175
|
scope,
|
|
174
176
|
module_cache: Rc::new(RefCell::new(HashMap::new())),
|
|
175
177
|
current_dir: RefCell::new(None),
|
|
178
|
+
virtual_builtins: Rc::new(RefCell::new(HashMap::new())),
|
|
176
179
|
}
|
|
177
180
|
}
|
|
178
181
|
|
|
@@ -187,6 +190,14 @@ impl Evaluator {
|
|
|
187
190
|
}
|
|
188
191
|
}
|
|
189
192
|
}
|
|
193
|
+
{
|
|
194
|
+
let mut vb = eval.virtual_builtins.borrow_mut();
|
|
195
|
+
for module in modules {
|
|
196
|
+
for (spec, value) in module.virtual_builtin_modules() {
|
|
197
|
+
vb.insert(Arc::from(spec), value);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
190
201
|
eval
|
|
191
202
|
}
|
|
192
203
|
|
|
@@ -502,7 +513,7 @@ impl Evaluator {
|
|
|
502
513
|
/// Load and evaluate a module, returning its exports object. Uses cache.
|
|
503
514
|
fn load_module(&mut self, from: &str) -> Result<Value, EvalError> {
|
|
504
515
|
if from.starts_with("tish:") {
|
|
505
|
-
return
|
|
516
|
+
return self.load_builtin_module(from);
|
|
506
517
|
}
|
|
507
518
|
let dir = self.current_dir.borrow().clone().ok_or_else(|| {
|
|
508
519
|
EvalError::Error("Cannot resolve imports: no current file directory (use run_file)".to_string())
|
|
@@ -582,8 +593,11 @@ impl Evaluator {
|
|
|
582
593
|
Ok(path)
|
|
583
594
|
}
|
|
584
595
|
|
|
585
|
-
/// Load built-in module (tish:fs, tish:http, tish:process)
|
|
586
|
-
fn load_builtin_module(spec: &str) -> Result<Value, EvalError> {
|
|
596
|
+
/// Load built-in module (tish:fs, tish:http, tish:process, …) or a virtual module from native crates.
|
|
597
|
+
fn load_builtin_module(&self, spec: &str) -> Result<Value, EvalError> {
|
|
598
|
+
if let Some(v) = self.virtual_builtins.borrow().get(spec) {
|
|
599
|
+
return Ok(v.clone());
|
|
600
|
+
}
|
|
587
601
|
match spec {
|
|
588
602
|
"tish:fs" => {
|
|
589
603
|
#[cfg(feature = "fs")]
|
|
@@ -675,15 +689,15 @@ impl Evaluator {
|
|
|
675
689
|
}
|
|
676
690
|
_ => {
|
|
677
691
|
return Err(EvalError::Error(format!(
|
|
678
|
-
"Unknown built-in module: {}. Supported: tish:fs, tish:http, tish:process, tish:ws",
|
|
692
|
+
"Unknown built-in module: {}. Supported: tish:fs, tish:http, tish:process, tish:ws (plus any registered by native modules)",
|
|
679
693
|
spec
|
|
680
694
|
)));
|
|
681
695
|
}
|
|
682
696
|
}
|
|
683
697
|
}
|
|
684
698
|
|
|
685
|
-
fn load_builtin_export(spec: &str, export_name: &str) -> Result<Value, EvalError> {
|
|
686
|
-
let module =
|
|
699
|
+
fn load_builtin_export(&self, spec: &str, export_name: &str) -> Result<Value, EvalError> {
|
|
700
|
+
let module = self.load_builtin_module(spec)?;
|
|
687
701
|
let exports = match &module {
|
|
688
702
|
Value::Object(m) => m.borrow().clone(),
|
|
689
703
|
_ => return Err(EvalError::Error("Built-in module must be object".into())),
|
|
@@ -1582,7 +1596,7 @@ impl Evaluator {
|
|
|
1582
1596
|
"JSX is not supported in the interpreter. Use 'tish compile --target js' to compile to JavaScript.".to_string(),
|
|
1583
1597
|
)),
|
|
1584
1598
|
Expr::NativeModuleLoad { spec, export_name, .. } => {
|
|
1585
|
-
|
|
1599
|
+
self.load_builtin_export(spec.as_ref(), export_name.as_ref())
|
|
1586
1600
|
}
|
|
1587
1601
|
Expr::TypeOf { operand, .. } => {
|
|
1588
1602
|
let v = self.eval_expr(operand)?;
|
|
@@ -2015,6 +2029,7 @@ impl Evaluator {
|
|
|
2015
2029
|
scope: Rc::clone(scope),
|
|
2016
2030
|
module_cache: Rc::clone(&self.module_cache),
|
|
2017
2031
|
current_dir: RefCell::new(self.current_dir.borrow().clone()),
|
|
2032
|
+
virtual_builtins: Rc::clone(&self.virtual_builtins),
|
|
2018
2033
|
};
|
|
2019
2034
|
match eval.eval_statement(body) {
|
|
2020
2035
|
Ok(v) => Ok(v),
|
|
@@ -2259,6 +2274,7 @@ impl Evaluator {
|
|
|
2259
2274
|
scope,
|
|
2260
2275
|
module_cache: Rc::clone(&self.module_cache),
|
|
2261
2276
|
current_dir: RefCell::new(self.current_dir.borrow().clone()),
|
|
2277
|
+
virtual_builtins: Rc::clone(&self.virtual_builtins),
|
|
2262
2278
|
};
|
|
2263
2279
|
match eval.eval_statement(body) {
|
|
2264
2280
|
Ok(v) => Ok(v),
|
|
@@ -14,6 +14,7 @@ pub mod regex;
|
|
|
14
14
|
mod value;
|
|
15
15
|
|
|
16
16
|
pub use eval::Evaluator;
|
|
17
|
+
pub use value::PropMap;
|
|
17
18
|
pub use value::Value;
|
|
18
19
|
|
|
19
20
|
/// Trait for pluggable native modules (e.g. Polars). Implement to register
|
|
@@ -21,6 +22,12 @@ pub use value::Value;
|
|
|
21
22
|
pub trait TishNativeModule: Send + Sync {
|
|
22
23
|
fn name(&self) -> &'static str;
|
|
23
24
|
fn register(&self) -> std::collections::HashMap<std::sync::Arc<str>, Value>;
|
|
25
|
+
|
|
26
|
+
/// Virtual `tish:*` modules for `import { x } from 'tish:…'` (e.g. `tish:polars`).
|
|
27
|
+
/// Return `(specifier, exports_object)` pairs. Default: none.
|
|
28
|
+
fn virtual_builtin_modules(&self) -> Vec<(&'static str, Value)> {
|
|
29
|
+
vec![]
|
|
30
|
+
}
|
|
24
31
|
}
|
|
25
32
|
#[cfg(feature = "regex")]
|
|
26
33
|
pub use regex::TishRegExp;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
//! LLVM
|
|
1
|
+
//! LLVM/clang link path for the **embedded-bytecode + VM** native binary.
|
|
2
2
|
//!
|
|
3
|
-
//!
|
|
4
|
-
//!
|
|
5
|
-
//!
|
|
3
|
+
//! Emits a C file that only holds the serialized chunk as bytes; clang produces `chunk.o`,
|
|
4
|
+
//! then links with **`tishlang_cranelift_runtime`** (same `tish_run_chunk` + `tishlang_vm`
|
|
5
|
+
//! entry as `--native-backend cranelift`). This is **not** LLVM IR lowering of Tish opcodes.
|
|
6
6
|
|
|
7
7
|
use std::fs;
|
|
8
8
|
use std::path::Path;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
//! Native code generation backend for Tish.
|
|
2
2
|
//!
|
|
3
|
-
//!
|
|
4
|
-
//!
|
|
5
|
-
//! -
|
|
3
|
+
//! - **`rust`:** `tishlang_compile` emits Rust calling **`tishlang_runtime`** (`Value`, etc.),
|
|
4
|
+
//! then `cargo build --release` links the user binary.
|
|
5
|
+
//! - **`cranelift`:** Embeds serialized bytecode in an object file and links **`tishlang_cranelift_runtime`**
|
|
6
|
+
//! — the executable runs **`tishlang_vm`** on that chunk (same as `tish run --backend vm`), not CLIF lowering.
|
|
7
|
+
//! - **`llvm`:** Same embedded-bytecode + VM link path via `tishlang_llvm` / shared linker.
|
|
6
8
|
//!
|
|
7
|
-
//!
|
|
8
|
-
//!
|
|
9
|
-
//! 2. Lower to Cranelift IR
|
|
10
|
-
//! 3. Emit .o via cranelift-object
|
|
11
|
-
//! 4. Link against prebuilt tishlang_runtime staticlib
|
|
9
|
+
//! **Future:** Lower bytecode (or typed IR) through Cranelift/LLVM to real machine code where semantics allow;
|
|
10
|
+
//! emit Rust using `Vec<f64>` / fixed primitives instead of `Value` on hot paths.
|
|
12
11
|
|
|
13
12
|
mod build;
|
|
14
13
|
|
|
@@ -31,9 +30,9 @@ impl std::error::Error for NativeError {}
|
|
|
31
30
|
|
|
32
31
|
/// Compile a Tish project to a native binary.
|
|
33
32
|
///
|
|
34
|
-
/// - `native_backend == "rust"`:
|
|
35
|
-
/// - `native_backend == "cranelift"`:
|
|
36
|
-
/// - `native_backend == "llvm"`:
|
|
33
|
+
/// - `native_backend == "rust"`: Rust source + `tishlang_runtime` + cargo (native imports).
|
|
34
|
+
/// - `native_backend == "cranelift"`: Embedded bytecode + VM binary (pure Tish only); not opcode AOT yet.
|
|
35
|
+
/// - `native_backend == "llvm"`: Embedded bytecode + VM via LLVM/clang link path.
|
|
37
36
|
pub fn compile_to_native(
|
|
38
37
|
entry_path: &Path,
|
|
39
38
|
project_root: Option<&Path>,
|
|
@@ -627,6 +627,12 @@ impl<'a> Parser<'a> {
|
|
|
627
627
|
span: self.span_end(span_start),
|
|
628
628
|
});
|
|
629
629
|
}
|
|
630
|
+
let type_ann = if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
|
|
631
|
+
self.advance();
|
|
632
|
+
Some(self.parse_type_annotation()?)
|
|
633
|
+
} else {
|
|
634
|
+
None
|
|
635
|
+
};
|
|
630
636
|
let init_expr = if matches!(self.peek_kind(), Some(TokenKind::Assign)) {
|
|
631
637
|
self.advance();
|
|
632
638
|
Some(self.parse_expr()?)
|
|
@@ -639,7 +645,7 @@ impl<'a> Parser<'a> {
|
|
|
639
645
|
Some(Box::new(Statement::VarDecl {
|
|
640
646
|
name,
|
|
641
647
|
mutable,
|
|
642
|
-
type_ann
|
|
648
|
+
type_ann,
|
|
643
649
|
init: init_expr,
|
|
644
650
|
span: self.span_end(var_span_start),
|
|
645
651
|
}))
|
|
@@ -211,6 +211,10 @@ impl TishOpaque for HttpReadableStream {
|
|
|
211
211
|
"ReadableStream"
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
fn as_any(&self) -> &dyn std::any::Any {
|
|
215
|
+
self
|
|
216
|
+
}
|
|
217
|
+
|
|
214
218
|
fn get_method(&self, name: &str) -> Option<NativeFn> {
|
|
215
219
|
if name != "getReader" {
|
|
216
220
|
return None;
|
|
@@ -247,6 +251,10 @@ impl TishOpaque for HttpStreamReader {
|
|
|
247
251
|
"ReadableStreamDefaultReader"
|
|
248
252
|
}
|
|
249
253
|
|
|
254
|
+
fn as_any(&self) -> &dyn std::any::Any {
|
|
255
|
+
self
|
|
256
|
+
}
|
|
257
|
+
|
|
250
258
|
fn get_method(&self, name: &str) -> Option<NativeFn> {
|
|
251
259
|
if name != "read" {
|
|
252
260
|
return None;
|
package/package.json
CHANGED
|
Binary file
|
package/platform/darwin-x64/tish
CHANGED
|
Binary file
|
|
Binary file
|
package/platform/linux-x64/tish
CHANGED
|
Binary file
|
|
Binary file
|