@tishlang/tish 1.6.0 → 1.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.
Files changed (113) hide show
  1. package/Cargo.toml +2 -0
  2. package/README.md +2 -0
  3. package/bin/tish +0 -0
  4. package/crates/js_to_tish/src/error.rs +2 -8
  5. package/crates/js_to_tish/src/transform/expr.rs +128 -137
  6. package/crates/js_to_tish/src/transform/stmt.rs +62 -32
  7. package/crates/tish/Cargo.toml +15 -5
  8. package/crates/tish/src/cargo_native_registry.rs +29 -0
  9. package/crates/tish/src/cli_help.rs +92 -39
  10. package/crates/tish/src/main.rs +172 -86
  11. package/crates/tish/src/repl_completion.rs +3 -3
  12. package/crates/tish/tests/cargo_example_compile.rs +4 -2
  13. package/crates/tish/tests/integration_test.rs +216 -54
  14. package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
  15. package/crates/tish/tests/shortcircuit.rs +20 -5
  16. package/crates/tish_ast/src/ast.rs +92 -23
  17. package/crates/tish_build_utils/Cargo.toml +4 -0
  18. package/crates/tish_build_utils/src/lib.rs +136 -8
  19. package/crates/tish_builtins/Cargo.toml +5 -1
  20. package/crates/tish_builtins/src/array.rs +65 -33
  21. package/crates/tish_builtins/src/construct.rs +34 -39
  22. package/crates/tish_builtins/src/globals.rs +42 -26
  23. package/crates/tish_builtins/src/helpers.rs +2 -1
  24. package/crates/tish_builtins/src/lib.rs +5 -5
  25. package/crates/tish_builtins/src/math.rs +5 -3
  26. package/crates/tish_builtins/src/object.rs +3 -2
  27. package/crates/tish_builtins/src/string.rs +144 -22
  28. package/crates/tish_bytecode/src/chunk.rs +0 -1
  29. package/crates/tish_bytecode/src/compiler.rs +173 -71
  30. package/crates/tish_bytecode/src/opcode.rs +24 -6
  31. package/crates/tish_bytecode/src/peephole.rs +2 -2
  32. package/crates/tish_compile/Cargo.toml +1 -0
  33. package/crates/tish_compile/src/codegen.rs +1621 -453
  34. package/crates/tish_compile/src/infer.rs +75 -19
  35. package/crates/tish_compile/src/lib.rs +19 -8
  36. package/crates/tish_compile/src/resolve.rs +278 -137
  37. package/crates/tish_compile/src/types.rs +184 -24
  38. package/crates/tish_compile_js/Cargo.toml +1 -0
  39. package/crates/tish_compile_js/src/codegen.rs +181 -37
  40. package/crates/tish_compile_js/src/lib.rs +3 -1
  41. package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
  42. package/crates/tish_compiler_wasm/src/lib.rs +16 -13
  43. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
  44. package/crates/tish_core/Cargo.toml +8 -0
  45. package/crates/tish_core/src/json.rs +107 -56
  46. package/crates/tish_core/src/lib.rs +4 -2
  47. package/crates/tish_core/src/macros.rs +5 -5
  48. package/crates/tish_core/src/uri.rs +9 -6
  49. package/crates/tish_core/src/value.rs +145 -43
  50. package/crates/tish_core/src/vmref.rs +178 -0
  51. package/crates/tish_cranelift/src/link.rs +6 -9
  52. package/crates/tish_cranelift/src/lower.rs +14 -8
  53. package/crates/tish_eval/Cargo.toml +17 -2
  54. package/crates/tish_eval/src/eval.rs +474 -165
  55. package/crates/tish_eval/src/http.rs +61 -0
  56. package/crates/tish_eval/src/lib.rs +12 -8
  57. package/crates/tish_eval/src/natives.rs +136 -38
  58. package/crates/tish_eval/src/promise.rs +14 -8
  59. package/crates/tish_eval/src/timers.rs +28 -19
  60. package/crates/tish_eval/src/value.rs +17 -6
  61. package/crates/tish_eval/src/value_convert.rs +13 -5
  62. package/crates/tish_fmt/src/lib.rs +149 -43
  63. package/crates/tish_lexer/src/lib.rs +232 -63
  64. package/crates/tish_lexer/src/token.rs +10 -6
  65. package/crates/tish_llvm/src/lib.rs +17 -8
  66. package/crates/tish_lsp/Cargo.toml +4 -1
  67. package/crates/tish_lsp/README.md +1 -1
  68. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  69. package/crates/tish_lsp/src/import_goto.rs +549 -0
  70. package/crates/tish_lsp/src/main.rs +504 -106
  71. package/crates/tish_native/src/build.rs +4 -8
  72. package/crates/tish_native/src/lib.rs +54 -21
  73. package/crates/tish_opt/src/lib.rs +84 -52
  74. package/crates/tish_parser/src/lib.rs +45 -13
  75. package/crates/tish_parser/src/parser.rs +505 -130
  76. package/crates/tish_resolve/Cargo.toml +13 -0
  77. package/crates/tish_resolve/src/lib.rs +3436 -0
  78. package/crates/tish_resolve/src/pos.rs +133 -0
  79. package/crates/tish_runtime/Cargo.toml +68 -3
  80. package/crates/tish_runtime/src/http.rs +1136 -145
  81. package/crates/tish_runtime/src/http_fetch.rs +38 -27
  82. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  83. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  84. package/crates/tish_runtime/src/lib.rs +375 -189
  85. package/crates/tish_runtime/src/promise.rs +199 -40
  86. package/crates/tish_runtime/src/promise_io.rs +2 -1
  87. package/crates/tish_runtime/src/timers.rs +37 -1
  88. package/crates/tish_runtime/src/ws.rs +65 -42
  89. package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
  90. package/crates/tish_ui/src/jsx.rs +317 -27
  91. package/crates/tish_ui/src/lib.rs +5 -2
  92. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  93. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  94. package/crates/tish_vm/Cargo.toml +15 -5
  95. package/crates/tish_vm/src/vm.rs +725 -281
  96. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
  97. package/crates/tish_wasm/src/lib.rs +55 -42
  98. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  99. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  100. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  101. package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
  102. package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
  103. package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
  104. package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
  105. package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
  106. package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
  107. package/justfile +8 -0
  108. package/package.json +1 -1
  109. package/platform/darwin-arm64/tish +0 -0
  110. package/platform/darwin-x64/tish +0 -0
  111. package/platform/linux-arm64/tish +0 -0
  112. package/platform/linux-x64/tish +0 -0
  113. package/platform/win32-x64/tish.exe +0 -0
@@ -1,11 +1,15 @@
1
1
  //! Code generation: AST -> Rust source.
2
2
 
3
+ use crate::resolve::is_builtin_native_spec;
4
+ use crate::types::{RustType, TypeContext};
3
5
  use std::borrow::Cow;
4
6
  use std::collections::{HashMap, HashSet};
5
7
  use std::path::Path;
6
- use tishlang_ast::{ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern, Expr, FunParam, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement, UnaryOp};
7
- use crate::resolve::is_builtin_native_spec;
8
- use crate::types::{RustType, TypeContext};
8
+ use tishlang_ast::{
9
+ ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern, Expr,
10
+ FunParam, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement,
11
+ TypeAnnotation, UnaryOp,
12
+ };
9
13
 
10
14
  /// Tracks variable usage for move/clone optimization.
11
15
  /// A variable can be moved instead of cloned if it's at its last use.
@@ -41,7 +45,12 @@ impl UsageAnalyzer {
41
45
  self.analyze_expr(e);
42
46
  }
43
47
  }
44
- Statement::If { cond, then_branch, else_branch, .. } => {
48
+ Statement::If {
49
+ cond,
50
+ then_branch,
51
+ else_branch,
52
+ ..
53
+ } => {
45
54
  self.analyze_expr(cond);
46
55
  self.analyze_statement(then_branch);
47
56
  if let Some(e) = else_branch {
@@ -49,7 +58,13 @@ impl UsageAnalyzer {
49
58
  }
50
59
  }
51
60
  Statement::Block { statements, .. } => self.analyze_statements(statements),
52
- Statement::For { init, cond, update, body, .. } => {
61
+ Statement::For {
62
+ init,
63
+ cond,
64
+ update,
65
+ body,
66
+ ..
67
+ } => {
53
68
  if let Some(i) = init {
54
69
  self.analyze_statement(i);
55
70
  }
@@ -69,7 +84,12 @@ impl UsageAnalyzer {
69
84
  self.analyze_expr(cond);
70
85
  self.analyze_statement(body);
71
86
  }
72
- Statement::Switch { expr, cases, default_body, .. } => {
87
+ Statement::Switch {
88
+ expr,
89
+ cases,
90
+ default_body,
91
+ ..
92
+ } => {
73
93
  self.analyze_expr(expr);
74
94
  for (case_expr, stmts) in cases {
75
95
  if let Some(e) = case_expr {
@@ -82,7 +102,12 @@ impl UsageAnalyzer {
82
102
  }
83
103
  }
84
104
  Statement::Throw { value, .. } => self.analyze_expr(value),
85
- Statement::Try { body, catch_body, finally_body, .. } => {
105
+ Statement::Try {
106
+ body,
107
+ catch_body,
108
+ finally_body,
109
+ ..
110
+ } => {
86
111
  self.analyze_statement(body);
87
112
  if let Some(c) = catch_body {
88
113
  self.analyze_statement(c);
@@ -95,7 +120,11 @@ impl UsageAnalyzer {
95
120
  self.analyze_statement(body);
96
121
  }
97
122
  Statement::Break { .. } | Statement::Continue { .. } => {}
98
- Statement::Import { .. } | Statement::Export { .. } => {
123
+ Statement::Import { .. }
124
+ | Statement::Export { .. }
125
+ | Statement::TypeAlias { .. }
126
+ | Statement::DeclareVar { .. }
127
+ | Statement::DeclareFun { .. } => {
99
128
  // Import/Export should be resolved by merge_modules before compilation
100
129
  }
101
130
  }
@@ -153,14 +182,17 @@ impl UsageAnalyzer {
153
182
  }
154
183
  }
155
184
  }
156
- Expr::ArrowFunction { body, .. } => {
157
- match body {
158
- ArrowBody::Expr(e) => self.analyze_expr(e),
159
- ArrowBody::Block(s) => self.analyze_statement(s),
160
- }
161
- }
185
+ Expr::ArrowFunction { body, .. } => match body {
186
+ ArrowBody::Expr(e) => self.analyze_expr(e),
187
+ ArrowBody::Block(s) => self.analyze_statement(s),
188
+ },
162
189
  Expr::Assign { value, .. } => self.analyze_expr(value),
163
- Expr::Conditional { cond, then_branch, else_branch, .. } => {
190
+ Expr::Conditional {
191
+ cond,
192
+ then_branch,
193
+ else_branch,
194
+ ..
195
+ } => {
164
196
  self.analyze_expr(cond);
165
197
  self.analyze_expr(then_branch);
166
198
  self.analyze_expr(else_branch);
@@ -183,23 +215,37 @@ impl UsageAnalyzer {
183
215
  *self.use_counts.entry(name.to_string()).or_insert(0) += 1;
184
216
  self.analyze_expr(value);
185
217
  }
186
- Expr::PostfixInc { name, .. } | Expr::PostfixDec { name, .. } | Expr::PrefixInc { name, .. } | Expr::PrefixDec { name, .. } => {
218
+ Expr::PostfixInc { name, .. }
219
+ | Expr::PostfixDec { name, .. }
220
+ | Expr::PrefixInc { name, .. }
221
+ | Expr::PrefixDec { name, .. } => {
187
222
  *self.use_counts.entry(name.to_string()).or_insert(0) += 1;
188
223
  }
189
224
  Expr::MemberAssign { object, value, .. } => {
190
225
  self.analyze_expr(object);
191
226
  self.analyze_expr(value);
192
227
  }
193
- Expr::IndexAssign { object, index, value, .. } => {
228
+ Expr::IndexAssign {
229
+ object,
230
+ index,
231
+ value,
232
+ ..
233
+ } => {
194
234
  self.analyze_expr(object);
195
235
  self.analyze_expr(index);
196
236
  self.analyze_expr(value);
197
237
  }
198
238
  Expr::Await { operand, .. } => self.analyze_expr(operand),
199
- Expr::JsxElement { props, children, .. } => {
239
+ Expr::JsxElement {
240
+ props, children, ..
241
+ } => {
200
242
  for p in props {
201
243
  match p {
202
- tishlang_ast::JsxProp::Attr { value: tishlang_ast::JsxAttrValue::Expr(e), .. } | tishlang_ast::JsxProp::Spread(e) => self.analyze_expr(e),
244
+ tishlang_ast::JsxProp::Attr {
245
+ value: tishlang_ast::JsxAttrValue::Expr(e),
246
+ ..
247
+ }
248
+ | tishlang_ast::JsxProp::Spread(e) => self.analyze_expr(e),
203
249
  _ => {}
204
250
  }
205
251
  }
@@ -240,7 +286,10 @@ pub struct CompileError {
240
286
 
241
287
  impl CompileError {
242
288
  fn new(msg: impl Into<String>, span: Option<Span>) -> Self {
243
- Self { message: msg.into(), span }
289
+ Self {
290
+ message: msg.into(),
291
+ span,
292
+ }
244
293
  }
245
294
  }
246
295
 
@@ -262,21 +311,45 @@ fn program_uses_async(program: &Program) -> bool {
262
311
  match s {
263
312
  Statement::FunDecl { async_, .. } if *async_ => true,
264
313
  Statement::Block { statements, .. } => statements.iter().any(stmt_has_async),
265
- Statement::If { then_branch, else_branch, .. } => {
266
- stmt_has_async(then_branch) || else_branch.as_ref().is_some_and(|s| stmt_has_async(s.as_ref()))
314
+ Statement::If {
315
+ then_branch,
316
+ else_branch,
317
+ ..
318
+ } => {
319
+ stmt_has_async(then_branch)
320
+ || else_branch
321
+ .as_ref()
322
+ .is_some_and(|s| stmt_has_async(s.as_ref()))
267
323
  }
268
- Statement::While { body, .. } | Statement::For { body, .. } | Statement::ForOf { body, .. }
324
+ Statement::While { body, .. }
325
+ | Statement::For { body, .. }
326
+ | Statement::ForOf { body, .. }
269
327
  | Statement::DoWhile { body, .. } => stmt_has_async(body),
270
- Statement::Switch { cases, default_body, .. } => {
271
- cases.iter().any(|(_, stmts)| stmts.iter().any(stmt_has_async))
328
+ Statement::Switch {
329
+ cases,
330
+ default_body,
331
+ ..
332
+ } => {
333
+ cases
334
+ .iter()
335
+ .any(|(_, stmts)| stmts.iter().any(stmt_has_async))
272
336
  || default_body
273
337
  .as_ref()
274
338
  .is_some_and(|stmts| stmts.iter().any(stmt_has_async))
275
339
  }
276
- Statement::Try { body, catch_body, finally_body, .. } => {
340
+ Statement::Try {
341
+ body,
342
+ catch_body,
343
+ finally_body,
344
+ ..
345
+ } => {
277
346
  stmt_has_async(body)
278
- || catch_body.as_ref().is_some_and(|s| stmt_has_async(s.as_ref()))
279
- || finally_body.as_ref().is_some_and(|s| stmt_has_async(s.as_ref()))
347
+ || catch_body
348
+ .as_ref()
349
+ .is_some_and(|s| stmt_has_async(s.as_ref()))
350
+ || finally_body
351
+ .as_ref()
352
+ .is_some_and(|s| stmt_has_async(s.as_ref()))
280
353
  }
281
354
  _ => false,
282
355
  }
@@ -287,14 +360,16 @@ fn program_uses_async(program: &Program) -> bool {
287
360
  Expr::Binary { left, right, .. } => expr_has_await(left) || expr_has_await(right),
288
361
  Expr::Unary { operand, .. } | Expr::TypeOf { operand, .. } => expr_has_await(operand),
289
362
  Expr::Call { callee, args, .. } => {
290
- expr_has_await(callee) || args.iter().any(|a| match a {
291
- CallArg::Expr(e) | CallArg::Spread(e) => expr_has_await(e),
292
- })
363
+ expr_has_await(callee)
364
+ || args.iter().any(|a| match a {
365
+ CallArg::Expr(e) | CallArg::Spread(e) => expr_has_await(e),
366
+ })
293
367
  }
294
368
  Expr::New { callee, args, .. } => {
295
- expr_has_await(callee) || args.iter().any(|a| match a {
296
- CallArg::Expr(e) | CallArg::Spread(e) => expr_has_await(e),
297
- })
369
+ expr_has_await(callee)
370
+ || args.iter().any(|a| match a {
371
+ CallArg::Expr(e) | CallArg::Spread(e) => expr_has_await(e),
372
+ })
298
373
  }
299
374
  Expr::Member { object, prop, .. } => {
300
375
  expr_has_await(object)
@@ -305,32 +380,48 @@ fn program_uses_async(program: &Program) -> bool {
305
380
  }
306
381
  }
307
382
  Expr::Index { object, index, .. } => expr_has_await(object) || expr_has_await(index),
308
- Expr::Conditional { cond, then_branch, else_branch, .. } => {
309
- expr_has_await(cond) || expr_has_await(then_branch) || expr_has_await(else_branch)
383
+ Expr::Conditional {
384
+ cond,
385
+ then_branch,
386
+ else_branch,
387
+ ..
388
+ } => expr_has_await(cond) || expr_has_await(then_branch) || expr_has_await(else_branch),
389
+ Expr::NullishCoalesce { left, right, .. } => {
390
+ expr_has_await(left) || expr_has_await(right)
310
391
  }
311
- Expr::NullishCoalesce { left, right, .. } => expr_has_await(left) || expr_has_await(right),
312
392
  Expr::Array { elements, .. } => elements.iter().any(|el| match el {
313
393
  ArrayElement::Expr(e) | ArrayElement::Spread(e) => expr_has_await(e),
314
394
  }),
315
395
  Expr::Object { props, .. } => props.iter().any(|p| match p {
316
396
  ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => expr_has_await(e),
317
397
  }),
318
- Expr::Assign { value, .. } | Expr::CompoundAssign { value, .. } | Expr::LogicalAssign { value, .. }
319
- | Expr::MemberAssign { value, .. } | Expr::IndexAssign { value, .. } => expr_has_await(value),
398
+ Expr::Assign { value, .. }
399
+ | Expr::CompoundAssign { value, .. }
400
+ | Expr::LogicalAssign { value, .. }
401
+ | Expr::MemberAssign { value, .. }
402
+ | Expr::IndexAssign { value, .. } => expr_has_await(value),
320
403
  Expr::ArrowFunction { body, .. } => match body {
321
404
  ArrowBody::Expr(e) => expr_has_await(e),
322
405
  ArrowBody::Block(s) => stmt_has_async(s),
323
406
  },
324
407
  Expr::TemplateLiteral { exprs, .. } => exprs.iter().any(expr_has_await),
325
- Expr::JsxElement { props, children, .. } => {
408
+ Expr::JsxElement {
409
+ props, children, ..
410
+ } => {
326
411
  props.iter().any(|p| match p {
327
- tishlang_ast::JsxProp::Attr { value: tishlang_ast::JsxAttrValue::Expr(e), .. } | tishlang_ast::JsxProp::Spread(e) => expr_has_await(e),
412
+ tishlang_ast::JsxProp::Attr {
413
+ value: tishlang_ast::JsxAttrValue::Expr(e),
414
+ ..
415
+ }
416
+ | tishlang_ast::JsxProp::Spread(e) => expr_has_await(e),
328
417
  _ => false,
329
- }) || children.iter().any(|c| matches!(c, tishlang_ast::JsxChild::Expr(e) if expr_has_await(e)))
330
- }
331
- Expr::JsxFragment { children, .. } => {
332
- children.iter().any(|c| matches!(c, tishlang_ast::JsxChild::Expr(e) if expr_has_await(e)))
418
+ }) || children
419
+ .iter()
420
+ .any(|c| matches!(c, tishlang_ast::JsxChild::Expr(e) if expr_has_await(e)))
333
421
  }
422
+ Expr::JsxFragment { children, .. } => children
423
+ .iter()
424
+ .any(|c| matches!(c, tishlang_ast::JsxChild::Expr(e) if expr_has_await(e))),
334
425
  _ => false,
335
426
  }
336
427
  }
@@ -340,21 +431,42 @@ fn program_uses_async(program: &Program) -> bool {
340
431
  Statement::VarDecl { init, .. } => init.as_ref().is_some_and(expr_has_await),
341
432
  Statement::VarDeclDestructure { init, .. } => expr_has_await(init),
342
433
  Statement::ExprStmt { expr, .. } => expr_has_await(expr),
343
- Statement::If { cond, then_branch, else_branch, .. } => {
344
- expr_has_await(cond) || stmt_has_await(then_branch)
345
- || else_branch.as_ref().is_some_and(|s| stmt_has_await(s.as_ref()))
434
+ Statement::If {
435
+ cond,
436
+ then_branch,
437
+ else_branch,
438
+ ..
439
+ } => {
440
+ expr_has_await(cond)
441
+ || stmt_has_await(then_branch)
442
+ || else_branch
443
+ .as_ref()
444
+ .is_some_and(|s| stmt_has_await(s.as_ref()))
346
445
  }
347
446
  Statement::While { cond, body, .. } => expr_has_await(cond) || stmt_has_await(body),
348
- Statement::For { init, cond, update, body, .. } => {
447
+ Statement::For {
448
+ init,
449
+ cond,
450
+ update,
451
+ body,
452
+ ..
453
+ } => {
349
454
  init.as_ref().is_some_and(|s| stmt_has_await(s.as_ref()))
350
455
  || cond.as_ref().is_some_and(expr_has_await)
351
456
  || update.as_ref().is_some_and(expr_has_await)
352
457
  || stmt_has_await(body)
353
458
  }
354
- Statement::ForOf { iterable, body, .. } => expr_has_await(iterable) || stmt_has_await(body),
459
+ Statement::ForOf { iterable, body, .. } => {
460
+ expr_has_await(iterable) || stmt_has_await(body)
461
+ }
355
462
  Statement::Return { value, .. } => value.as_ref().is_some_and(expr_has_await),
356
463
  Statement::FunDecl { body, .. } => stmt_has_await(body),
357
- Statement::Switch { expr, cases, default_body, .. } => {
464
+ Statement::Switch {
465
+ expr,
466
+ cases,
467
+ default_body,
468
+ ..
469
+ } => {
358
470
  expr_has_await(expr)
359
471
  || cases.iter().any(|(c, stmts)| {
360
472
  c.as_ref().is_some_and(expr_has_await) || stmts.iter().any(stmt_has_await)
@@ -365,23 +477,38 @@ fn program_uses_async(program: &Program) -> bool {
365
477
  }
366
478
  Statement::DoWhile { body, cond, .. } => stmt_has_await(body) || expr_has_await(cond),
367
479
  Statement::Throw { value, .. } => expr_has_await(value),
368
- Statement::Try { body, catch_body, finally_body, .. } => {
480
+ Statement::Try {
481
+ body,
482
+ catch_body,
483
+ finally_body,
484
+ ..
485
+ } => {
369
486
  stmt_has_await(body)
370
- || catch_body.as_ref().is_some_and(|s| stmt_has_await(s.as_ref()))
371
- || finally_body.as_ref().is_some_and(|s| stmt_has_await(s.as_ref()))
487
+ || catch_body
488
+ .as_ref()
489
+ .is_some_and(|s| stmt_has_await(s.as_ref()))
490
+ || finally_body
491
+ .as_ref()
492
+ .is_some_and(|s| stmt_has_await(s.as_ref()))
372
493
  }
373
494
  Statement::Import { .. } | Statement::Export { .. } => false,
374
495
  _ => false,
375
496
  }
376
497
  }
377
- program.statements.iter().any(|s| stmt_has_async(s) || stmt_has_await(s))
498
+ program
499
+ .statements
500
+ .iter()
501
+ .any(|s| stmt_has_async(s) || stmt_has_await(s))
378
502
  }
379
503
 
380
504
  pub fn compile(program: &Program) -> Result<String, CompileError> {
381
505
  compile_with_project_root(program, None)
382
506
  }
383
507
 
384
- pub fn compile_with_project_root(program: &Program, project_root: Option<&Path>) -> Result<String, CompileError> {
508
+ pub fn compile_with_project_root(
509
+ program: &Program,
510
+ project_root: Option<&Path>,
511
+ ) -> Result<String, CompileError> {
385
512
  compile_with_features(program, project_root, &[])
386
513
  }
387
514
 
@@ -416,24 +543,40 @@ pub fn compile_project_full(
416
543
  > {
417
544
  use crate::resolve;
418
545
  let root = project_root.unwrap_or_else(|| entry_path.parent().unwrap_or(Path::new(".")));
419
- let modules = resolve::resolve_project(entry_path, project_root)
420
- .map_err(|e| CompileError { message: e, span: None })?;
421
- resolve::detect_cycles(&modules)
422
- .map_err(|e| CompileError { message: e, span: None })?;
423
- let merged = resolve::merge_modules(modules)
424
- .map_err(|e| CompileError { message: e, span: None })?;
425
- let native_modules = resolve::resolve_native_modules(&merged, root)
426
- .map_err(|e| CompileError { message: e, span: None })?;
427
- let native_build = resolve::compute_native_build_artifacts(&merged, root, &native_modules)
428
- .map_err(|e| CompileError { message: e, span: None })?;
546
+ let modules = resolve::resolve_project(entry_path, project_root).map_err(|e| CompileError {
547
+ message: e,
548
+ span: None,
549
+ })?;
550
+ resolve::detect_cycles(&modules).map_err(|e| CompileError {
551
+ message: e,
552
+ span: None,
553
+ })?;
554
+ let merged = resolve::merge_modules(modules).map_err(|e| CompileError {
555
+ message: e,
556
+ span: None,
557
+ })?;
558
+ let native_modules =
559
+ resolve::resolve_native_modules(&merged.program, root).map_err(|e| CompileError {
560
+ message: e,
561
+ span: None,
562
+ })?;
563
+ let native_build = resolve::compute_native_build_artifacts(
564
+ &merged.program,
565
+ root,
566
+ &native_modules,
567
+ )
568
+ .map_err(|e| CompileError {
569
+ message: e,
570
+ span: None,
571
+ })?;
429
572
  let mut all_features: Vec<String> = features.to_vec();
430
- for f in resolve::extract_native_import_features(&merged) {
573
+ for f in resolve::extract_native_import_features(&merged.program) {
431
574
  if !all_features.contains(&f) {
432
575
  all_features.push(f);
433
576
  }
434
577
  }
435
578
  let rust = compile_with_native_modules(
436
- &merged,
579
+ &merged.program,
437
580
  project_root,
438
581
  &all_features,
439
582
  &native_modules,
@@ -463,7 +606,11 @@ pub fn compile_with_native_modules(
463
606
  native_init: &std::collections::HashMap<String, crate::resolve::NativeModuleInit>,
464
607
  optimize: bool,
465
608
  ) -> Result<String, CompileError> {
466
- let program = if optimize { tishlang_opt::optimize(program) } else { program.clone() };
609
+ let program = if optimize {
610
+ tishlang_opt::optimize(program)
611
+ } else {
612
+ program.clone()
613
+ };
467
614
  // Type-inference pass: fills in `type_ann` on unannotated VarDecl nodes where
468
615
  // the type is unambiguous (literals, arithmetic of typed vars, etc.).
469
616
  let program = crate::infer::infer_program(&program);
@@ -521,8 +668,20 @@ struct Codegen {
521
668
  usage_analyzer: Option<UsageAnalyzer>,
522
669
  /// Type context for tracking variable types (for static typing)
523
670
  type_context: TypeContext,
671
+ /// Registry of `type Foo = { ... }` declarations seen in the program.
672
+ /// Populated in a pre-pass so that any later `let x: Foo = ...` or
673
+ /// `fn f(x: Foo)` resolves to a `RustType::Named { name: "Foo", ... }`
674
+ /// and the codegen can emit a Rust struct + direct field access for
675
+ /// values of that type.
676
+ type_aliases: std::collections::HashMap<String, crate::types::RustType>,
524
677
  /// Program uses JSX; emit `tishlang_ui` imports and `h` / `Fragment` globals.
525
678
  program_has_jsx: bool,
679
+ /// `fn` names for Rust JSX: PascalCase tags matching these use a value binding; others are string intrinsics.
680
+ program_fun_decl_names: std::collections::HashSet<String>,
681
+ /// Nesting depth inside `Value::native(move |args| {{ ... }})` user functions / arrows.
682
+ /// `try`/`throw` lowering uses `return Err` only at depth 0 (e.g. `run()`); inside native
683
+ /// closures it must not return a `Result` from a `Value`-returning closure.
684
+ value_fn_depth: u32,
526
685
  }
527
686
 
528
687
  impl Codegen {
@@ -549,7 +708,191 @@ impl Codegen {
549
708
  rc_cell_storage_scopes: vec![std::collections::HashSet::new()],
550
709
  usage_analyzer: None,
551
710
  type_context: TypeContext::new(),
711
+ type_aliases: std::collections::HashMap::new(),
552
712
  program_has_jsx: false,
713
+ program_fun_decl_names: std::collections::HashSet::new(),
714
+ value_fn_depth: 0,
715
+ }
716
+ }
717
+
718
+ /// Walk every `Statement::TypeAlias` in the program (including nested
719
+ /// ones inside blocks, ifs, loops, function bodies, and exports) and
720
+ /// register the resolved `RustType` under its alias name. Forward
721
+ /// references are handled by running this pass *before* any other
722
+ /// codegen step.
723
+ fn collect_type_aliases(&mut self, statements: &[Statement]) {
724
+ // Two passes so an alias `type B = A` can resolve `A` even if
725
+ // `A` is declared after `B` in source order.
726
+ let mut raw: Vec<(String, &TypeAnnotation)> = Vec::new();
727
+ Self::walk_type_aliases(statements, &mut raw);
728
+ // First-fixpoint resolution: keep iterating until no more aliases
729
+ // change shape. In practice 1–2 passes; capped to prevent infinite
730
+ // loops on (already rejected) self-referential aliases.
731
+ for _ in 0..8 {
732
+ let mut changed = false;
733
+ for (name, ann) in &raw {
734
+ let resolved = crate::types::RustType::from_annotation_with_aliases(
735
+ ann,
736
+ &self.type_aliases,
737
+ );
738
+ let prev: Option<crate::types::RustType> =
739
+ self.type_aliases.get(name).cloned();
740
+ if prev.as_ref() != Some(&resolved) {
741
+ self.type_aliases.insert(name.clone(), resolved);
742
+ changed = true;
743
+ }
744
+ }
745
+ if !changed {
746
+ break;
747
+ }
748
+ }
749
+ }
750
+
751
+ fn walk_type_aliases<'p>(
752
+ statements: &'p [Statement],
753
+ out: &mut Vec<(String, &'p TypeAnnotation)>,
754
+ ) {
755
+ for s in statements {
756
+ match s {
757
+ Statement::TypeAlias { name, ty, .. } => {
758
+ out.push((name.to_string(), ty));
759
+ }
760
+ Statement::Block { statements, .. } => Self::walk_type_aliases(statements, out),
761
+ Statement::If {
762
+ then_branch,
763
+ else_branch,
764
+ ..
765
+ } => {
766
+ Self::walk_type_aliases(std::slice::from_ref(then_branch.as_ref()), out);
767
+ if let Some(e) = else_branch {
768
+ Self::walk_type_aliases(std::slice::from_ref(e.as_ref()), out);
769
+ }
770
+ }
771
+ Statement::For { body, .. }
772
+ | Statement::ForOf { body, .. }
773
+ | Statement::While { body, .. }
774
+ | Statement::DoWhile { body, .. } => {
775
+ Self::walk_type_aliases(std::slice::from_ref(body.as_ref()), out);
776
+ }
777
+ Statement::Export { declaration, .. } => {
778
+ if let tishlang_ast::ExportDeclaration::Named(s) = declaration.as_ref() {
779
+ Self::walk_type_aliases(std::slice::from_ref(s.as_ref()), out);
780
+ }
781
+ }
782
+ _ => {}
783
+ }
784
+ }
785
+ }
786
+
787
+ /// Emit a Rust `struct` definition for every type alias whose RHS is
788
+ /// an object shape. Each generated struct derives `Clone` + `Debug`
789
+ /// (cheap; field types are all `Copy`-or-cheap-clone in practice) and
790
+ /// is named `TishStruct_<TishAlias>`.
791
+ fn emit_named_struct_decls(&mut self) {
792
+ // Snapshot keys + values so we can mutate `self` (writing the
793
+ // emitted source) inside the loop.
794
+ let mut entries: Vec<(String, crate::types::RustType)> =
795
+ self.type_aliases.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
796
+ entries.sort_by(|a, b| a.0.cmp(&b.0));
797
+ let mut emitted_any = false;
798
+ for (name, ty) in entries {
799
+ if let crate::types::RustType::Named { fields, .. }
800
+ | crate::types::RustType::Object(fields)
801
+ // ^^ also accept inline shapes registered as aliases — though
802
+ // `from_annotation_with_aliases` should always have lifted
803
+ // them to `Named` by now.
804
+ = &ty
805
+ {
806
+ let struct_name = crate::types::named_struct_ident(&name);
807
+ self.write(&format!("#[derive(Clone, Debug, Default)]\n"));
808
+ self.write("#[allow(non_snake_case, non_camel_case_types)]\n");
809
+ self.write(&format!("pub struct {} {{\n", struct_name));
810
+ for (k, t) in fields {
811
+ self.write(&format!(
812
+ " pub {}: {},\n",
813
+ crate::types::field_ident(k),
814
+ t.to_rust_type_str()
815
+ ));
816
+ }
817
+ self.write("}\n\n");
818
+
819
+ // Emit a hand-rolled JSON serialiser per struct so
820
+ // `JSON.stringify(typed_value)` (and `Vec<TishStruct_X>`)
821
+ // can bypass the `Value::Object` allocation entirely —
822
+ // we walk the struct's fields by name and write directly
823
+ // into the response buffer. ASCII-fast string escape is
824
+ // shared with the `Value` path via the
825
+ // `escape_json_string_into` helper that the runtime
826
+ // re-exports from `tishlang_core::json`.
827
+ self.write(&format!("impl {} {{\n", struct_name));
828
+ self.write(" pub fn _tish_write_json(&self, buf: &mut String) {\n");
829
+ self.write(" use std::fmt::Write as _;\n");
830
+ self.write(" buf.push('{');\n");
831
+ for (i, (k, t)) in fields.iter().enumerate() {
832
+ let sep = if i == 0 { "{" } else { ",{" };
833
+ let prefix = if i == 0 {
834
+ format!("\"\\\"{}\\\":\"", k.as_ref())
835
+ } else {
836
+ format!("\",\\\"{}\\\":\"", k.as_ref())
837
+ };
838
+ let _ = sep; // (lint silence; we built `prefix` above directly)
839
+ self.write(&format!(" buf.push_str({});\n", prefix));
840
+ let access = format!("self.{}", crate::types::field_ident(k));
841
+ match t {
842
+ crate::types::RustType::F64 => {
843
+ self.write(&format!(
844
+ " if {a}.is_nan() || {a}.is_infinite() {{ buf.push_str(\"null\"); }} else {{ let _ = write!(buf, \"{{}}\", {a}); }}\n",
845
+ a = access
846
+ ));
847
+ }
848
+ crate::types::RustType::Bool => {
849
+ self.write(&format!(
850
+ " buf.push_str(if {} {{ \"true\" }} else {{ \"false\" }});\n",
851
+ access
852
+ ));
853
+ }
854
+ crate::types::RustType::String => {
855
+ self.write(&format!(
856
+ " buf.push('\"'); tishlang_runtime::json::escape_into(buf, {}.as_str()); buf.push('\"');\n",
857
+ access
858
+ ));
859
+ }
860
+ crate::types::RustType::Named { .. } => {
861
+ self.write(&format!(
862
+ " {}._tish_write_json(buf);\n",
863
+ access
864
+ ));
865
+ }
866
+ crate::types::RustType::Vec(inner) if matches!(
867
+ inner.as_ref(),
868
+ crate::types::RustType::Named { .. }
869
+ ) => {
870
+ self.write(" buf.push('[');\n");
871
+ self.write(&format!(
872
+ " for (i, item) in {}.iter().enumerate() {{ if i > 0 {{ buf.push(','); }} item._tish_write_json(buf); }}\n",
873
+ access
874
+ ));
875
+ self.write(" buf.push(']');\n");
876
+ }
877
+ _ => {
878
+ // Fallback: convert the field to a Value and
879
+ // delegate to the dynamic stringifier.
880
+ let v_expr = t.to_value_expr(&access);
881
+ self.write(&format!(
882
+ " let _v: Value = {}; tishlang_runtime::json::stringify_into(buf, &_v);\n",
883
+ v_expr
884
+ ));
885
+ }
886
+ }
887
+ }
888
+ self.write(" buf.push('}');\n");
889
+ self.write(" }\n");
890
+ self.write("}\n\n");
891
+ emitted_any = true;
892
+ }
893
+ }
894
+ if emitted_any {
895
+ self.write("\n");
553
896
  }
554
897
  }
555
898
 
@@ -595,39 +938,47 @@ impl Codegen {
595
938
  fn builtin_native_module_rust_init(&self, spec: &str, export_name: &str) -> Option<String> {
596
939
  let init = match spec {
597
940
  "tish:fs" if self.has_feature("fs") => match export_name {
598
- "readFile" => Some("Value::Function(Rc::new(|args: &[Value]| tish_read_file(args)))"),
599
- "writeFile" => Some("Value::Function(Rc::new(|args: &[Value]| tish_write_file(args)))"),
600
- "fileExists" => Some("Value::Function(Rc::new(|args: &[Value]| tish_file_exists(args)))"),
601
- "isDir" => Some("Value::Function(Rc::new(|args: &[Value]| tish_is_dir(args)))"),
602
- "readDir" => Some("Value::Function(Rc::new(|args: &[Value]| tish_read_dir(args)))"),
603
- "mkdir" => Some("Value::Function(Rc::new(|args: &[Value]| tish_mkdir(args)))"),
941
+ "readFile" => Some("Value::native(|args: &[Value]| tish_read_file(args))"),
942
+ "writeFile" => Some("Value::native(|args: &[Value]| tish_write_file(args))"),
943
+ "fileExists" => Some("Value::native(|args: &[Value]| tish_file_exists(args))"),
944
+ "isDir" => Some("Value::native(|args: &[Value]| tish_is_dir(args))"),
945
+ "readDir" => Some("Value::native(|args: &[Value]| tish_read_dir(args))"),
946
+ "mkdir" => Some("Value::native(|args: &[Value]| tish_mkdir(args))"),
604
947
  _ => None,
605
948
  },
606
949
  "tish:http" if self.has_feature("http") => match export_name {
607
- "fetch" => Some("Value::Function(Rc::new(|args: &[Value]| tish_fetch_promise(args.to_vec())))"),
608
- "fetchAll" => Some("Value::Function(Rc::new(|args: &[Value]| tish_fetch_all_promise(args.to_vec())))"),
609
- "serve" => Some("Value::Function(Rc::new(|args: &[Value]| { let port = args.first().cloned().unwrap_or(Value::Null); let handler = args.get(1).cloned().unwrap_or(Value::Null); if let Value::Function(f) = handler { tish_http_serve(args, move |req_args| f(req_args)) } else { Value::Null } }))"),
950
+ "fetch" => Some("Value::native(|args: &[Value]| tish_fetch_promise(args.to_vec()))"),
951
+ "fetchAll" => Some("Value::native(|args: &[Value]| tish_fetch_all_promise(args.to_vec()))"),
952
+ // `serve(port, handler)` (single shared handler) or
953
+ // `serve(port, { onWorker })` (per-worker factory). The
954
+ // latter dispatches into `http_serve_per_worker`, which
955
+ // calls onWorker once per accept thread to build that
956
+ // thread's handler.
957
+ "serve" => Some("Value::native(|args: &[Value]| { let handler = args.get(1).cloned().unwrap_or(Value::Null); match handler { Value::Function(f) => tish_http_serve(args, move |req_args| f(req_args)), Value::Object(ref opts) => { let factory = opts.borrow().get(&Arc::from(\"onWorker\")).cloned().unwrap_or(Value::Null); tishlang_runtime::http_serve_per_worker(args, factory) }, _ => Value::Null } })"),
610
958
  "Promise" => Some("tish_promise_object()"),
611
- "setTimeout" => Some("Value::Function(Rc::new(|args: &[Value]| tish_timer_set_timeout(args)))"),
612
- "setInterval" => Some("Value::Function(Rc::new(|_args: &[Value]| panic!(\"setInterval not yet supported in native\")))"),
613
- "clearTimeout" => Some("Value::Function(Rc::new(|args: &[Value]| tish_timer_clear_timeout(args)))"),
614
- "clearInterval" => Some("Value::Function(Rc::new(|_args: &[Value]| Value::Null))"),
959
+ _ => None,
960
+ },
961
+ "tish:timers" if self.has_feature("timers") => match export_name {
962
+ "setTimeout" => Some("Value::native(|args: &[Value]| tish_timer_set_timeout(args))"),
963
+ "setInterval" => Some("Value::native(|args: &[Value]| tish_timer_set_interval(args))"),
964
+ "clearTimeout" => Some("Value::native(|args: &[Value]| tish_timer_clear_timeout(args))"),
965
+ "clearInterval" => Some("Value::native(|args: &[Value]| tish_timer_clear_interval(args))"),
615
966
  _ => None,
616
967
  },
617
968
  "tish:process" if self.has_feature("process") => match export_name {
618
- "exit" => Some("Value::Function(Rc::new(|args: &[Value]| tish_process_exit(args)))"),
619
- "cwd" => Some("Value::Function(Rc::new(|args: &[Value]| tish_process_cwd(args)))"),
620
- "exec" => Some("Value::Function(Rc::new(|args: &[Value]| tish_process_exec(args)))"),
621
- "argv" => Some("Value::Array(Rc::new(RefCell::new(std::env::args().map(|s| Value::String(s.into())).collect())))"),
622
- "env" => Some("Value::Object(Rc::new(RefCell::new(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect())))"),
623
- "process" => Some("{ let mut m = ObjectMap::default(); m.insert(Arc::from(\"exit\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exit(args)))); m.insert(Arc::from(\"cwd\"), Value::Function(Rc::new(|args: &[Value]| tish_process_cwd(args)))); m.insert(Arc::from(\"exec\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exec(args)))); m.insert(Arc::from(\"argv\"), Value::Array(Rc::new(RefCell::new(std::env::args().map(|s| Value::String(s.into())).collect())))); m.insert(Arc::from(\"env\"), Value::Object(Rc::new(RefCell::new(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect::<ObjectMap>())))); Value::Object(Rc::new(RefCell::new(m))) }"),
969
+ "exit" => Some("Value::native(|args: &[Value]| tish_process_exit(args))"),
970
+ "cwd" => Some("Value::native(|args: &[Value]| tish_process_cwd(args))"),
971
+ "exec" => Some("Value::native(|args: &[Value]| tish_process_exec(args))"),
972
+ "argv" => Some("Value::Array(VmRef::new(std::env::args().map(|s| Value::String(s.into())).collect()))"),
973
+ "env" => Some("Value::Object(VmRef::new(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect()))"),
974
+ "process" => Some("{ let mut m = ObjectMap::default(); m.insert(Arc::from(\"exit\"), Value::native(|args: &[Value]| tish_process_exit(args))); m.insert(Arc::from(\"cwd\"), Value::native(|args: &[Value]| tish_process_cwd(args))); m.insert(Arc::from(\"exec\"), Value::native(|args: &[Value]| tish_process_exec(args))); m.insert(Arc::from(\"argv\"), Value::Array(VmRef::new(std::env::args().map(|s| Value::String(s.into())).collect()))); m.insert(Arc::from(\"env\"), Value::Object(VmRef::new(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect::<ObjectMap>()))); Value::Object(VmRef::new(m)) }"),
624
975
  _ => None,
625
976
  },
626
977
  "tish:ws" if self.has_feature("ws") => match export_name {
627
- "WebSocket" => Some("Value::Function(Rc::new(|args: &[Value]| tish_ws_client(args)))"),
628
- "Server" => Some("Value::Function(Rc::new(|args: &[Value]| tish_ws_server_construct(args)))"),
629
- "wsSend" => Some("Value::Function(Rc::new(|args: &[Value]| Value::Bool(tishlang_runtime::ws_send_native(args.first().unwrap_or(&Value::Null), &args.get(1).map(|v| v.to_display_string()).unwrap_or_default()))))"),
630
- "wsBroadcast" => Some("Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::ws_broadcast_native(args)))"),
978
+ "WebSocket" => Some("Value::native(|args: &[Value]| tish_ws_client(args))"),
979
+ "Server" => Some("Value::native(|args: &[Value]| tish_ws_server_construct(args))"),
980
+ "wsSend" => Some("Value::native(|args: &[Value]| Value::Bool(tishlang_runtime::ws_send_native(args.first().unwrap_or(&Value::Null), &args.get(1).map(|v| v.to_display_string()).unwrap_or_default())))"),
981
+ "wsBroadcast" => Some("Value::native(|args: &[Value]| tishlang_runtime::ws_broadcast_native(args))"),
631
982
  _ => None,
632
983
  },
633
984
  _ => return None,
@@ -637,7 +988,7 @@ impl Codegen {
637
988
 
638
989
  fn has_feature(&self, name: &str) -> bool {
639
990
  if self.features.contains("full") {
640
- matches!(name, "http" | "fs" | "process" | "regex" | "ws")
991
+ matches!(name, "http" | "timers" | "fs" | "process" | "regex" | "ws")
641
992
  } else {
642
993
  self.features.contains(name)
643
994
  }
@@ -668,17 +1019,23 @@ impl Codegen {
668
1019
  /// Escape Rust reserved keywords by prefixing with r#
669
1020
  fn escape_ident(name: &str) -> Cow<'_, str> {
670
1021
  // Rust standard library macros that conflict with variable names
671
- const RUST_MACROS: &[&str] = &["line", "column", "file", "module_path", "stringify", "concat"];
1022
+ const RUST_MACROS: &[&str] = &[
1023
+ "line",
1024
+ "column",
1025
+ "file",
1026
+ "module_path",
1027
+ "stringify",
1028
+ "concat",
1029
+ ];
672
1030
  if RUST_MACROS.contains(&name) {
673
1031
  return Cow::Owned(format!("r#{}", name));
674
1032
  }
675
1033
  const RUST_KEYWORDS: &[&str] = &[
676
- "as", "async", "await", "break", "const", "continue", "crate", "dyn",
677
- "else", "enum", "extern", "false", "fn", "for", "if", "impl", "in",
678
- "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return",
679
- "self", "Self", "static", "struct", "super", "trait", "true", "type",
680
- "unsafe", "use", "where", "while", "abstract", "become", "box", "do",
681
- "final", "macro", "override", "priv", "try", "typeof", "unsized",
1034
+ "as", "async", "await", "break", "const", "continue", "crate", "dyn", "else", "enum",
1035
+ "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod",
1036
+ "move", "mut", "pub", "ref", "return", "self", "Self", "static", "struct", "super",
1037
+ "trait", "true", "type", "unsafe", "use", "where", "while", "abstract", "become",
1038
+ "box", "do", "final", "macro", "override", "priv", "try", "typeof", "unsized",
682
1039
  "virtual", "yield",
683
1040
  ];
684
1041
  if RUST_KEYWORDS.contains(&name) {
@@ -717,20 +1074,20 @@ impl Codegen {
717
1074
  if !Self::needs_clone(expr) {
718
1075
  return false;
719
1076
  }
720
-
1077
+
721
1078
  // Check for last-use optimization on simple identifiers
722
1079
  if let Expr::Ident { name, .. } = expr {
723
1080
  // Don't optimize RefCell-wrapped vars (they're borrowed, not owned)
724
1081
  if self.refcell_wrapped_vars.contains(name.as_ref()) {
725
1082
  return true;
726
1083
  }
727
-
1084
+
728
1085
  // Inside a loop, any variable used in an init (e.g. "let x = outerVar") must be cloned:
729
1086
  // the loop body runs multiple times, so we cannot move on the first iteration.
730
1087
  if !self.loop_stack.is_empty() {
731
1088
  return true;
732
1089
  }
733
-
1090
+
734
1091
  // Check if this is the last use
735
1092
  if let Some(ref mut analyzer) = self.usage_analyzer {
736
1093
  if analyzer.is_last_use(name.as_ref()) {
@@ -738,7 +1095,7 @@ impl Codegen {
738
1095
  }
739
1096
  }
740
1097
  }
741
-
1098
+
742
1099
  true
743
1100
  }
744
1101
 
@@ -806,7 +1163,7 @@ impl Codegen {
806
1163
  /// Returns Some(true) for ascending, Some(false) for descending, None if not detected
807
1164
  fn detect_numeric_sort_comparator(expr: &Expr) -> Option<bool> {
808
1165
  use tishlang_ast::ArrowBody;
809
-
1166
+
810
1167
  if let Expr::ArrowFunction { params, body, .. } = expr {
811
1168
  if params.len() != 2 {
812
1169
  return None;
@@ -819,7 +1176,7 @@ impl Codegen {
819
1176
  }
820
1177
  _ => return None,
821
1178
  };
822
-
1179
+
823
1180
  // Body must be a single expression that's a subtraction
824
1181
  let body_expr = match body {
825
1182
  ArrowBody::Expr(e) => e.as_ref(),
@@ -831,10 +1188,24 @@ impl Codegen {
831
1188
  }
832
1189
  }
833
1190
  };
834
-
835
- if let Expr::Binary { left, op: BinOp::Sub, right, .. } = body_expr {
1191
+
1192
+ if let Expr::Binary {
1193
+ left,
1194
+ op: BinOp::Sub,
1195
+ right,
1196
+ ..
1197
+ } = body_expr
1198
+ {
836
1199
  // Check for a - b (ascending) or b - a (descending)
837
- if let (Expr::Ident { name: left_name, .. }, Expr::Ident { name: right_name, .. }) = (left.as_ref(), right.as_ref()) {
1200
+ if let (
1201
+ Expr::Ident {
1202
+ name: left_name, ..
1203
+ },
1204
+ Expr::Ident {
1205
+ name: right_name, ..
1206
+ },
1207
+ ) = (left.as_ref(), right.as_ref())
1208
+ {
838
1209
  if left_name.as_ref() == param_a && right_name.as_ref() == param_b {
839
1210
  return Some(true); // ascending
840
1211
  }
@@ -850,22 +1221,26 @@ impl Codegen {
850
1221
  fn emit_program(&mut self, program: &Program) -> Result<(), CompileError> {
851
1222
  self.is_async = program_uses_async(program);
852
1223
  self.program_has_jsx = tishlang_ui::jsx::program_contains_jsx(program);
1224
+ self.program_fun_decl_names = tishlang_ui::jsx::collect_fun_decl_names(program);
853
1225
  self.write("#![allow(unused, non_snake_case)]\n\n");
854
1226
  self.write("use std::cell::RefCell;\n");
855
1227
  self.write("use std::rc::Rc;\n");
856
1228
  self.write("use std::sync::Arc;\n");
857
- self.write("use tishlang_runtime::{console_debug as tish_console_debug, console_info as tish_console_info, console_log as tish_console_log, console_warn as tish_console_warn, console_error as tish_console_error, boolean as tish_boolean, decode_uri as tish_decode_uri, encode_uri as tish_encode_uri, in_operator as tish_in_operator, is_finite as tish_is_finite, is_nan as tish_is_nan, json_parse as tish_json_parse, json_stringify as tish_json_stringify, math_abs as tish_math_abs, math_ceil as tish_math_ceil, math_floor as tish_math_floor, math_max as tish_math_max, math_min as tish_math_min, math_round as tish_math_round, math_sqrt as tish_math_sqrt, parse_float as tish_parse_float, parse_int as tish_parse_int, math_random as tish_math_random, math_pow as tish_math_pow, math_sin as tish_math_sin, math_cos as tish_math_cos, math_tan as tish_math_tan, math_log as tish_math_log, math_exp as tish_math_exp, math_sign as tish_math_sign, math_trunc as tish_math_trunc, date_now as tish_date_now, array_is_array as tish_array_is_array, string_from_char_code as tish_string_from_char_code, object_assign as tish_object_assign, object_keys as tish_object_keys, object_values as tish_object_values, object_entries as tish_object_entries, object_from_entries as tish_object_from_entries, tish_construct, tish_uint8_array_constructor, tish_audio_context_constructor, ObjectMap, TishError, Value};\n");
1229
+ self.write("use tishlang_runtime::{console_debug as tish_console_debug, console_info as tish_console_info, console_log as tish_console_log, console_warn as tish_console_warn, console_error as tish_console_error, boolean as tish_boolean, decode_uri as tish_decode_uri, encode_uri as tish_encode_uri, string_escape_html_impl as tish_escape_html, in_operator as tish_in_operator, is_finite as tish_is_finite, is_nan as tish_is_nan, json_parse as tish_json_parse, json_stringify as tish_json_stringify, math_abs as tish_math_abs, math_ceil as tish_math_ceil, math_floor as tish_math_floor, math_max as tish_math_max, math_min as tish_math_min, math_round as tish_math_round, math_sqrt as tish_math_sqrt, parse_float as tish_parse_float, parse_int as tish_parse_int, math_random as tish_math_random, math_pow as tish_math_pow, math_sin as tish_math_sin, math_cos as tish_math_cos, math_tan as tish_math_tan, math_log as tish_math_log, math_exp as tish_math_exp, math_sign as tish_math_sign, math_trunc as tish_math_trunc, date_now as tish_date_now, array_is_array as tish_array_is_array, string_from_char_code as tish_string_from_char_code, object_assign as tish_object_assign, object_keys as tish_object_keys, object_values as tish_object_values, object_entries as tish_object_entries, object_from_entries as tish_object_from_entries, tish_construct, tish_uint8_array_constructor, tish_audio_context_constructor, register_static_route as tish_register_static_route, ObjectMap, TishError, Value, VmRef};\n");
858
1230
  if self.program_has_jsx {
859
1231
  self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
860
1232
  }
861
1233
  if self.has_feature("process") {
862
1234
  self.write("use tishlang_runtime::{process_exit as tish_process_exit, process_cwd as tish_process_cwd, process_exec as tish_process_exec};\n");
863
1235
  }
1236
+ if self.has_feature("timers") {
1237
+ self.write("use tishlang_runtime::{timer_set_timeout as tish_timer_set_timeout, timer_clear_timeout as tish_timer_clear_timeout, timer_set_interval as tish_timer_set_interval, timer_clear_interval as tish_timer_clear_interval};\n");
1238
+ }
864
1239
  if self.has_feature("http") {
865
1240
  if self.is_async {
866
- self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve, timer_set_timeout as tish_timer_set_timeout, timer_clear_timeout as tish_timer_clear_timeout, promise_object as tish_promise_object, await_promise as tish_await_promise};\n");
1241
+ self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve, promise_object as tish_promise_object, await_promise as tish_await_promise};\n");
867
1242
  } else {
868
- self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve, timer_set_timeout as tish_timer_set_timeout, timer_clear_timeout as tish_timer_clear_timeout};\n");
1243
+ self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve};\n");
869
1244
  }
870
1245
  }
871
1246
  if self.has_feature("fs") {
@@ -879,6 +1254,19 @@ impl Codegen {
879
1254
  }
880
1255
  self.write("\n");
881
1256
 
1257
+ // Collect every `type Foo = { ... }` declaration in the program
1258
+ // (recursive, so they can also live inside blocks / branches) and
1259
+ // canonicalise each into a `RustType::Named` with its field list.
1260
+ // Aliases that resolve to a non-Object shape (e.g. `type N = number`)
1261
+ // are stored too, so later annotations like `let x: N = 0` still
1262
+ // pick up the right native type.
1263
+ self.collect_type_aliases(&program.statements);
1264
+ // Emit a Rust `struct` for every alias whose RHS is an object
1265
+ // shape. Subsequent `let x: Foo = ...` literals lower to plain
1266
+ // struct moves (no `VmRef::new(ObjectMap::from(..))` allocation),
1267
+ // and `x.field` becomes a direct field access.
1268
+ self.emit_named_struct_decls();
1269
+
882
1270
  if self.is_async {
883
1271
  self.writeln("#[tokio::main]");
884
1272
  self.writeln("async fn main() {");
@@ -907,150 +1295,220 @@ impl Codegen {
907
1295
  self.indent += 1;
908
1296
 
909
1297
  // Initialize builtins
910
- self.writeln("let mut console = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
1298
+ self.writeln("let mut console = Value::Object(VmRef::new(ObjectMap::from([");
911
1299
  self.indent += 1;
912
- self.writeln("(Arc::from(\"debug\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_debug(args); Value::Null }))),");
913
- self.writeln("(Arc::from(\"info\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_info(args); Value::Null }))),");
914
- self.writeln("(Arc::from(\"log\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_log(args); Value::Null }))),");
915
- self.writeln("(Arc::from(\"warn\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_warn(args); Value::Null }))),");
916
- self.writeln("(Arc::from(\"error\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_error(args); Value::Null }))),");
1300
+ self.writeln("(Arc::from(\"debug\"), Value::native(|args: &[Value]| { tish_console_debug(args); Value::Null })),");
1301
+ self.writeln("(Arc::from(\"info\"), Value::native(|args: &[Value]| { tish_console_info(args); Value::Null })),");
1302
+ self.writeln("(Arc::from(\"log\"), Value::native(|args: &[Value]| { tish_console_log(args); Value::Null })),");
1303
+ self.writeln("(Arc::from(\"warn\"), Value::native(|args: &[Value]| { tish_console_warn(args); Value::Null })),");
1304
+ self.writeln("(Arc::from(\"error\"), Value::native(|args: &[Value]| { tish_console_error(args); Value::Null })),");
917
1305
  self.indent -= 1;
918
- self.writeln("]))));");
919
- self.writeln("let Boolean = Value::Function(Rc::new(|args: &[Value]| tish_boolean(args)));");
920
- self.writeln("let parseInt = Value::Function(Rc::new(|args: &[Value]| tish_parse_int(args)));");
921
- self.writeln("let parseFloat = Value::Function(Rc::new(|args: &[Value]| tish_parse_float(args)));");
922
- self.writeln("let decodeURI = Value::Function(Rc::new(|args: &[Value]| tish_decode_uri(args)));");
923
- self.writeln("let encodeURI = Value::Function(Rc::new(|args: &[Value]| tish_encode_uri(args)));");
924
- self.writeln("let isFinite = Value::Function(Rc::new(|args: &[Value]| tish_is_finite(args)));");
925
- self.writeln("let isNaN = Value::Function(Rc::new(|args: &[Value]| tish_is_nan(args)));");
1306
+ self.writeln("])));");
1307
+ self.writeln(
1308
+ "let Boolean = Value::native(|args: &[Value]| tish_boolean(args));",
1309
+ );
1310
+ self.writeln(
1311
+ "let parseInt = Value::native(|args: &[Value]| tish_parse_int(args));",
1312
+ );
1313
+ self.writeln(
1314
+ "let parseFloat = Value::native(|args: &[Value]| tish_parse_float(args));",
1315
+ );
1316
+ self.writeln(
1317
+ "let decodeURI = Value::native(|args: &[Value]| tish_decode_uri(args));",
1318
+ );
1319
+ self.writeln(
1320
+ "let encodeURI = Value::native(|args: &[Value]| tish_encode_uri(args));",
1321
+ );
1322
+ self.writeln(
1323
+ r#"let registerStaticRoute = Value::native(|args: &[Value]| { let path = match args.get(0) { Some(Value::String(s)) => s.to_string(), _ => return Value::Null }; let body = match args.get(1) { Some(Value::String(s)) => s.as_bytes().to_vec(), _ => return Value::Null }; let ct = match args.get(2) { Some(Value::String(s)) => s.to_string(), _ => "application/octet-stream".to_string() }; tish_register_static_route(&path, &body, &ct); Value::Null });"#,
1324
+ );
1325
+ self.writeln(
1326
+ "let htmlEscape = Value::native(|args: &[Value]| tish_escape_html(args.first().unwrap_or(&Value::Null)));",
1327
+ );
1328
+ self.writeln(
1329
+ "let isFinite = Value::native(|args: &[Value]| tish_is_finite(args));",
1330
+ );
1331
+ self.writeln("let isNaN = Value::native(|args: &[Value]| tish_is_nan(args));");
926
1332
  self.writeln("let Infinity = Value::Number(f64::INFINITY);");
927
1333
  self.writeln("let NaN = Value::Number(f64::NAN);");
928
- self.writeln("let Math = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
1334
+ self.writeln("let Math = Value::Object(VmRef::new(ObjectMap::from([");
929
1335
  self.indent += 1;
930
- self.writeln("(Arc::from(\"abs\"), Value::Function(Rc::new(|args: &[Value]| tish_math_abs(args)))),");
931
- self.writeln("(Arc::from(\"sqrt\"), Value::Function(Rc::new(|args: &[Value]| tish_math_sqrt(args)))),");
932
- self.writeln("(Arc::from(\"min\"), Value::Function(Rc::new(|args: &[Value]| tish_math_min(args)))),");
933
- self.writeln("(Arc::from(\"max\"), Value::Function(Rc::new(|args: &[Value]| tish_math_max(args)))),");
934
- self.writeln("(Arc::from(\"floor\"), Value::Function(Rc::new(|args: &[Value]| tish_math_floor(args)))),");
935
- self.writeln("(Arc::from(\"ceil\"), Value::Function(Rc::new(|args: &[Value]| tish_math_ceil(args)))),");
936
- self.writeln("(Arc::from(\"round\"), Value::Function(Rc::new(|args: &[Value]| tish_math_round(args)))),");
937
- self.writeln("(Arc::from(\"random\"), Value::Function(Rc::new(|args: &[Value]| tish_math_random(args)))),");
938
- self.writeln("(Arc::from(\"pow\"), Value::Function(Rc::new(|args: &[Value]| tish_math_pow(args)))),");
939
- self.writeln("(Arc::from(\"sin\"), Value::Function(Rc::new(|args: &[Value]| tish_math_sin(args)))),");
940
- self.writeln("(Arc::from(\"cos\"), Value::Function(Rc::new(|args: &[Value]| tish_math_cos(args)))),");
941
- self.writeln("(Arc::from(\"tan\"), Value::Function(Rc::new(|args: &[Value]| tish_math_tan(args)))),");
942
- self.writeln("(Arc::from(\"log\"), Value::Function(Rc::new(|args: &[Value]| tish_math_log(args)))),");
943
- self.writeln("(Arc::from(\"exp\"), Value::Function(Rc::new(|args: &[Value]| tish_math_exp(args)))),");
944
- self.writeln("(Arc::from(\"sign\"), Value::Function(Rc::new(|args: &[Value]| tish_math_sign(args)))),");
945
- self.writeln("(Arc::from(\"trunc\"), Value::Function(Rc::new(|args: &[Value]| tish_math_trunc(args)))),");
1336
+ self.writeln(
1337
+ "(Arc::from(\"abs\"), Value::native(|args: &[Value]| tish_math_abs(args))),",
1338
+ );
1339
+ self.writeln("(Arc::from(\"sqrt\"), Value::native(|args: &[Value]| tish_math_sqrt(args))),");
1340
+ self.writeln(
1341
+ "(Arc::from(\"min\"), Value::native(|args: &[Value]| tish_math_min(args))),",
1342
+ );
1343
+ self.writeln(
1344
+ "(Arc::from(\"max\"), Value::native(|args: &[Value]| tish_math_max(args))),",
1345
+ );
1346
+ self.writeln("(Arc::from(\"floor\"), Value::native(|args: &[Value]| tish_math_floor(args))),");
1347
+ self.writeln("(Arc::from(\"ceil\"), Value::native(|args: &[Value]| tish_math_ceil(args))),");
1348
+ self.writeln("(Arc::from(\"round\"), Value::native(|args: &[Value]| tish_math_round(args))),");
1349
+ self.writeln("(Arc::from(\"random\"), Value::native(|args: &[Value]| tish_math_random(args))),");
1350
+ self.writeln(
1351
+ "(Arc::from(\"pow\"), Value::native(|args: &[Value]| tish_math_pow(args))),",
1352
+ );
1353
+ self.writeln(
1354
+ "(Arc::from(\"sin\"), Value::native(|args: &[Value]| tish_math_sin(args))),",
1355
+ );
1356
+ self.writeln(
1357
+ "(Arc::from(\"cos\"), Value::native(|args: &[Value]| tish_math_cos(args))),",
1358
+ );
1359
+ self.writeln(
1360
+ "(Arc::from(\"tan\"), Value::native(|args: &[Value]| tish_math_tan(args))),",
1361
+ );
1362
+ self.writeln(
1363
+ "(Arc::from(\"log\"), Value::native(|args: &[Value]| tish_math_log(args))),",
1364
+ );
1365
+ self.writeln(
1366
+ "(Arc::from(\"exp\"), Value::native(|args: &[Value]| tish_math_exp(args))),",
1367
+ );
1368
+ self.writeln("(Arc::from(\"sign\"), Value::native(|args: &[Value]| tish_math_sign(args))),");
1369
+ self.writeln("(Arc::from(\"trunc\"), Value::native(|args: &[Value]| tish_math_trunc(args))),");
946
1370
  self.writeln("(Arc::from(\"PI\"), Value::Number(std::f64::consts::PI)),");
947
1371
  self.writeln("(Arc::from(\"E\"), Value::Number(std::f64::consts::E)),");
948
1372
  self.indent -= 1;
949
- self.writeln("]))));");
950
- self.writeln("let JSON = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
1373
+ self.writeln("])));");
1374
+ self.writeln("let JSON = Value::Object(VmRef::new(ObjectMap::from([");
951
1375
  self.indent += 1;
952
- self.writeln("(Arc::from(\"parse\"), Value::Function(Rc::new(|args: &[Value]| tish_json_parse(args)))),");
953
- self.writeln("(Arc::from(\"stringify\"), Value::Function(Rc::new(|args: &[Value]| tish_json_stringify(args)))),");
1376
+ self.writeln("(Arc::from(\"parse\"), Value::native(|args: &[Value]| tish_json_parse(args))),");
1377
+ self.writeln("(Arc::from(\"stringify\"), Value::native(|args: &[Value]| tish_json_stringify(args))),");
954
1378
  self.indent -= 1;
955
- self.writeln("]))));");
1379
+ self.writeln("])));");
956
1380
 
957
- self.writeln("let Array = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
1381
+ self.writeln("let Array = Value::Object(VmRef::new(ObjectMap::from([");
958
1382
  self.indent += 1;
959
- self.writeln("(Arc::from(\"isArray\"), Value::Function(Rc::new(|args: &[Value]| tish_array_is_array(args)))),");
1383
+ self.writeln("(Arc::from(\"isArray\"), Value::native(|args: &[Value]| tish_array_is_array(args))),");
960
1384
  self.indent -= 1;
961
- self.writeln("]))));");
1385
+ self.writeln("])));");
962
1386
 
963
- self.writeln("let String = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
1387
+ self.writeln("let String = Value::Object(VmRef::new(ObjectMap::from([");
964
1388
  self.indent += 1;
965
- self.writeln("(Arc::from(\"fromCharCode\"), Value::Function(Rc::new(|args: &[Value]| tish_string_from_char_code(args)))),");
1389
+ self.writeln("(Arc::from(\"fromCharCode\"), Value::native(|args: &[Value]| tish_string_from_char_code(args))),");
966
1390
  self.indent -= 1;
967
- self.writeln("]))));");
1391
+ self.writeln("])));");
968
1392
 
969
- self.writeln("let Date = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
1393
+ self.writeln("let Date = Value::Object(VmRef::new(ObjectMap::from([");
970
1394
  self.indent += 1;
971
- self.writeln("(Arc::from(\"now\"), Value::Function(Rc::new(|args: &[Value]| tish_date_now(args)))),");
1395
+ self.writeln(
1396
+ "(Arc::from(\"now\"), Value::native(|args: &[Value]| tish_date_now(args))),",
1397
+ );
972
1398
  self.indent -= 1;
973
- self.writeln("]))));");
1399
+ self.writeln("])));");
974
1400
 
975
- self.writeln("let Object = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
1401
+ self.writeln("let Object = Value::Object(VmRef::new(ObjectMap::from([");
976
1402
  self.indent += 1;
977
- self.writeln("(Arc::from(\"assign\"), Value::Function(Rc::new(|args: &[Value]| tish_object_assign(args)))),");
978
- self.writeln("(Arc::from(\"keys\"), Value::Function(Rc::new(|args: &[Value]| tish_object_keys(args)))),");
979
- self.writeln("(Arc::from(\"values\"), Value::Function(Rc::new(|args: &[Value]| tish_object_values(args)))),");
980
- self.writeln("(Arc::from(\"entries\"), Value::Function(Rc::new(|args: &[Value]| tish_object_entries(args)))),");
981
- self.writeln("(Arc::from(\"fromEntries\"), Value::Function(Rc::new(|args: &[Value]| tish_object_from_entries(args)))),");
1403
+ self.writeln("(Arc::from(\"assign\"), Value::native(|args: &[Value]| tish_object_assign(args))),");
1404
+ self.writeln("(Arc::from(\"keys\"), Value::native(|args: &[Value]| tish_object_keys(args))),");
1405
+ self.writeln("(Arc::from(\"values\"), Value::native(|args: &[Value]| tish_object_values(args))),");
1406
+ self.writeln("(Arc::from(\"entries\"), Value::native(|args: &[Value]| tish_object_entries(args))),");
1407
+ self.writeln("(Arc::from(\"fromEntries\"), Value::native(|args: &[Value]| tish_object_from_entries(args))),");
982
1408
  self.indent -= 1;
983
- self.writeln("]))));");
1409
+ self.writeln("])));");
984
1410
 
985
1411
  self.writeln("let Uint8Array = tish_uint8_array_constructor();");
986
1412
  self.writeln("let AudioContext = tish_audio_context_constructor();");
987
1413
 
988
1414
  if self.has_feature("process") {
989
- self.writeln("let process = Value::Object(Rc::new(RefCell::new({");
1415
+ self.writeln("let process = Value::Object(VmRef::new({");
990
1416
  self.indent += 1;
991
1417
  self.writeln("let mut p = ObjectMap::default();");
992
- self.writeln("p.insert(Arc::from(\"exit\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exit(args))));");
993
- self.writeln("p.insert(Arc::from(\"cwd\"), Value::Function(Rc::new(|args: &[Value]| tish_process_cwd(args))));");
994
- self.writeln("p.insert(Arc::from(\"exec\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exec(args))));");
1418
+ self.writeln("p.insert(Arc::from(\"exit\"), Value::native(|args: &[Value]| tish_process_exit(args)));");
1419
+ self.writeln("p.insert(Arc::from(\"cwd\"), Value::native(|args: &[Value]| tish_process_cwd(args)));");
1420
+ self.writeln("p.insert(Arc::from(\"exec\"), Value::native(|args: &[Value]| tish_process_exec(args)));");
995
1421
  self.writeln("let argv: Vec<Value> = std::env::args().map(|s| Value::String(s.into())).collect();");
996
- self.writeln("p.insert(Arc::from(\"argv\"), Value::Array(Rc::new(RefCell::new(argv))));");
1422
+ self.writeln(
1423
+ "p.insert(Arc::from(\"argv\"), Value::Array(VmRef::new(argv)));",
1424
+ );
997
1425
  self.writeln("let mut env_obj = ObjectMap::default();");
998
1426
  self.writeln("for (key, value) in std::env::vars() {");
999
1427
  self.indent += 1;
1000
1428
  self.writeln("env_obj.insert(Arc::from(key.as_str()), Value::String(value.into()));");
1001
1429
  self.indent -= 1;
1002
1430
  self.writeln("}");
1003
- self.writeln("p.insert(Arc::from(\"env\"), Value::Object(Rc::new(RefCell::new(env_obj))));");
1431
+ self.writeln(
1432
+ "p.insert(Arc::from(\"env\"), Value::Object(VmRef::new(env_obj)));",
1433
+ );
1004
1434
  self.writeln("p");
1005
1435
  self.indent -= 1;
1006
- self.writeln("})));");
1436
+ self.writeln("}));");
1007
1437
  }
1008
1438
 
1439
+ if self.has_feature("timers") {
1440
+ self.writeln("let setTimeout = Value::native(|args: &[Value]| tish_timer_set_timeout(args));");
1441
+ self.writeln("let clearTimeout = Value::native(|args: &[Value]| tish_timer_clear_timeout(args));");
1442
+ self.writeln("let setInterval = Value::native(|args: &[Value]| tish_timer_set_interval(args));");
1443
+ self.writeln("let clearInterval = Value::native(|args: &[Value]| tish_timer_clear_interval(args));");
1444
+ }
1009
1445
  if self.has_feature("http") {
1010
- self.writeln("let fetch = Value::Function(Rc::new(|args: &[Value]| tish_fetch_promise(args.to_vec())));");
1011
- self.writeln("let fetchAll = Value::Function(Rc::new(|args: &[Value]| tish_fetch_all_promise(args.to_vec())));");
1012
- self.writeln("let setTimeout = Value::Function(Rc::new(|args: &[Value]| tish_timer_set_timeout(args)));");
1013
- self.writeln("let clearTimeout = Value::Function(Rc::new(|args: &[Value]| tish_timer_clear_timeout(args)));");
1446
+ self.writeln("let fetch = Value::native(|args: &[Value]| tish_fetch_promise(args.to_vec()));");
1447
+ self.writeln("let fetchAll = Value::native(|args: &[Value]| tish_fetch_all_promise(args.to_vec()));");
1014
1448
  if self.is_async {
1015
1449
  self.writeln("let Promise = tish_promise_object();");
1016
1450
  }
1017
- self.writeln("let serve = Value::Function(Rc::new(|args: &[Value]| {");
1451
+ // `serve` supports two shapes:
1452
+ // 1. serve(port, handler) // single shared handler
1453
+ // 2. serve(port, { onWorker: (workerId) => handler, ... })
1454
+ //
1455
+ // Shape (2) lets users build per-worker state (DB connection,
1456
+ // cache, counter, ...) without a global mutex. The runtime
1457
+ // dispatches each accept thread to its own handler, all in
1458
+ // parallel under `send-values`.
1459
+ self.writeln("let serve = Value::native(|args: &[Value]| {");
1018
1460
  self.indent += 1;
1019
- self.writeln("let port = args.first().cloned().unwrap_or(Value::Null);");
1020
1461
  self.writeln("let handler = args.get(1).cloned().unwrap_or(Value::Null);");
1021
- self.writeln("if let Value::Function(f) = handler {");
1462
+ self.writeln("match handler {");
1022
1463
  self.indent += 1;
1023
- self.writeln("tish_http_serve(args, move |req_args| f(req_args))");
1024
- self.indent -= 1;
1025
- self.writeln("} else {");
1464
+ self.writeln("Value::Function(f) => tish_http_serve(args, move |req_args| f(req_args)),");
1465
+ self.writeln("Value::Object(ref opts) => {");
1026
1466
  self.indent += 1;
1027
- self.writeln("Value::Null");
1467
+ self.writeln("let factory = opts.borrow().get(&Arc::from(\"onWorker\")).cloned().unwrap_or(Value::Null);");
1468
+ self.writeln("tishlang_runtime::http_serve_per_worker(args, factory)");
1469
+ self.indent -= 1;
1470
+ self.writeln("},");
1471
+ self.writeln("_ => Value::Null,");
1028
1472
  self.indent -= 1;
1029
1473
  self.writeln("}");
1030
1474
  self.indent -= 1;
1031
- self.writeln("}));");
1475
+ self.writeln("});");
1032
1476
  }
1033
1477
 
1034
1478
  if self.has_feature("fs") {
1035
- self.writeln("let readFile = Value::Function(Rc::new(|args: &[Value]| tish_read_file(args)));");
1036
- self.writeln("let writeFile = Value::Function(Rc::new(|args: &[Value]| tish_write_file(args)));");
1037
- self.writeln("let fileExists = Value::Function(Rc::new(|args: &[Value]| tish_file_exists(args)));");
1038
- self.writeln("let isDir = Value::Function(Rc::new(|args: &[Value]| tish_is_dir(args)));");
1039
- self.writeln("let readDir = Value::Function(Rc::new(|args: &[Value]| tish_read_dir(args)));");
1040
- self.writeln("let mkdir = Value::Function(Rc::new(|args: &[Value]| tish_mkdir(args)));");
1479
+ self.writeln(
1480
+ "let readFile = Value::native(|args: &[Value]| tish_read_file(args));",
1481
+ );
1482
+ self.writeln(
1483
+ "let writeFile = Value::native(|args: &[Value]| tish_write_file(args));",
1484
+ );
1485
+ self.writeln("let fileExists = Value::native(|args: &[Value]| tish_file_exists(args));");
1486
+ self.writeln(
1487
+ "let isDir = Value::native(|args: &[Value]| tish_is_dir(args));",
1488
+ );
1489
+ self.writeln(
1490
+ "let readDir = Value::native(|args: &[Value]| tish_read_dir(args));",
1491
+ );
1492
+ self.writeln(
1493
+ "let mkdir = Value::native(|args: &[Value]| tish_mkdir(args));",
1494
+ );
1041
1495
  }
1042
1496
 
1043
1497
  if self.has_feature("regex") {
1044
- self.writeln("let RegExp = Value::Function(Rc::new(|args: &[Value]| regexp_new(args)));");
1498
+ self.writeln(
1499
+ "let RegExp = Value::native(|args: &[Value]| regexp_new(args));",
1500
+ );
1045
1501
  }
1046
1502
 
1047
1503
  if self.program_has_jsx {
1048
1504
  self.writeln("install_thread_local_host(Box::new(HeadlessHost::default()));");
1049
1505
  self.writeln("let Fragment = fragment_value();");
1050
- self.writeln("let h = Value::Function(Rc::new(|args: &[Value]| ui_h(args)));");
1051
- self.writeln("let text = Value::Function(Rc::new(|args: &[Value]| ui_text(args)));");
1052
- self.writeln("let useState = Value::Function(Rc::new(|args: &[Value]| native_use_state(args)));");
1053
- self.writeln("let createRoot = Value::Function(Rc::new(|args: &[Value]| native_create_root(args)));");
1506
+ self.writeln("let h = Value::native(|args: &[Value]| ui_h(args));");
1507
+ self.writeln("let text = Value::native(|args: &[Value]| ui_text(args));");
1508
+ self.writeln(
1509
+ "let useState = Value::native(|args: &[Value]| native_use_state(args));",
1510
+ );
1511
+ self.writeln("let createRoot = Value::native(|args: &[Value]| native_create_root(args));");
1054
1512
  }
1055
1513
 
1056
1514
  // Polars, Egui etc. are emitted via VarDecl from import { X } from 'tish:...'
@@ -1060,7 +1518,10 @@ impl Codegen {
1060
1518
  *self.function_scope_stack.last_mut().unwrap() = top_level_funcs.clone();
1061
1519
  for func_name in &top_level_funcs {
1062
1520
  let escaped = Self::escape_ident(func_name);
1063
- self.writeln(&format!("let {}_cell: Rc<RefCell<Value>> = Rc::new(RefCell::new(Value::Null));", escaped));
1521
+ self.writeln(&format!(
1522
+ "let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
1523
+ escaped
1524
+ ));
1064
1525
  }
1065
1526
 
1066
1527
  // Initialize usage analyzer for move/clone optimization
@@ -1097,9 +1558,11 @@ impl Codegen {
1097
1558
  self.indent += 1;
1098
1559
  self.type_context.push_scope();
1099
1560
  self.outer_vars_stack.push(Vec::new());
1100
- self.rc_cell_storage_scopes.push(std::collections::HashSet::new());
1561
+ self.rc_cell_storage_scopes
1562
+ .push(std::collections::HashSet::new());
1101
1563
  // Prepass: vars that must be RefCell because nested closures capture and mutate them
1102
- let vars_mutated_by_nested = Self::collect_vars_mutated_by_nested_closures(statements);
1564
+ let vars_mutated_by_nested =
1565
+ Self::collect_vars_mutated_by_nested_closures(statements);
1103
1566
  for v in &vars_mutated_by_nested {
1104
1567
  self.refcell_wrapped_vars.insert(v.clone());
1105
1568
  }
@@ -1109,7 +1572,10 @@ impl Codegen {
1109
1572
  // Create cells for all functions in this scope
1110
1573
  for func_name in &func_names {
1111
1574
  let escaped = Self::escape_ident(func_name);
1112
- self.writeln(&format!("let {}_cell: Rc<RefCell<Value>> = Rc::new(RefCell::new(Value::Null));", escaped));
1575
+ self.writeln(&format!(
1576
+ "let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
1577
+ escaped
1578
+ ));
1113
1579
  }
1114
1580
  for s in statements {
1115
1581
  self.emit_statement(s)?;
@@ -1124,19 +1590,30 @@ impl Codegen {
1124
1590
  self.indent -= 1;
1125
1591
  self.writeln("}");
1126
1592
  }
1127
- Statement::VarDecl { name, mutable, type_ann, init, .. } => {
1128
- // Determine the Rust type from annotation
1593
+ Statement::VarDecl {
1594
+ name,
1595
+ mutable,
1596
+ type_ann,
1597
+ init,
1598
+ ..
1599
+ } => {
1600
+ // Determine the Rust type from annotation, consulting the
1601
+ // user-declared `type` aliases so a `let x: World = ...`
1602
+ // resolves to `RustType::Named { name: "World", fields }`
1603
+ // and we can emit a struct move instead of a Value box.
1129
1604
  let rust_type = type_ann
1130
1605
  .as_ref()
1131
- .map(RustType::from_annotation)
1606
+ .map(|t| {
1607
+ crate::types::RustType::from_annotation_with_aliases(t, &self.type_aliases)
1608
+ })
1132
1609
  .unwrap_or(RustType::Value);
1133
1610
 
1134
1611
  // Track the variable type
1135
1612
  self.type_context.define(name.as_ref(), rust_type.clone());
1136
-
1613
+
1137
1614
  let mutability = if *mutable { "let mut" } else { "let" };
1138
1615
  let escaped_name = Self::escape_ident(name.as_ref());
1139
-
1616
+
1140
1617
  if rust_type.is_native() {
1141
1618
  // Generate native typed variable
1142
1619
  let expr_str = match init.as_ref() {
@@ -1146,7 +1623,7 @@ impl Codegen {
1146
1623
  if self.refcell_wrapped_vars.contains(name.as_ref()) {
1147
1624
  // Closure-mutated: same Rc<RefCell<T>> pattern as Value (assignments use borrow_mut)
1148
1625
  self.writeln(&format!(
1149
- "let {} = std::rc::Rc::new(RefCell::new({}));",
1626
+ "let {} = VmRef::new({});",
1150
1627
  escaped_name, expr_str
1151
1628
  ));
1152
1629
  self.rc_cell_storage_define(name.as_ref());
@@ -1164,8 +1641,7 @@ impl Codegen {
1164
1641
  let s = self.emit_expr(e)?;
1165
1642
  // Variable refs (Ident) in init must always clone: they may be used
1166
1643
  // multiple times (e.g. in a loop body) and we cannot move.
1167
- let needs = matches!(e, Expr::Ident { .. })
1168
- || self.should_clone(e);
1644
+ let needs = matches!(e, Expr::Ident { .. }) || self.should_clone(e);
1169
1645
  (s, needs)
1170
1646
  }
1171
1647
  None => ("Value::Null".to_string(), false),
@@ -1177,25 +1653,42 @@ impl Codegen {
1177
1653
  } else {
1178
1654
  expr_str.to_string()
1179
1655
  };
1180
- self.writeln(&format!("let {} = std::rc::Rc::new(RefCell::new({}));", escaped_name, init_val));
1656
+ self.writeln(&format!(
1657
+ "let {} = VmRef::new({});",
1658
+ escaped_name, init_val
1659
+ ));
1181
1660
  self.rc_cell_storage_define(name.as_ref());
1182
1661
  } else if clone_needed {
1183
- self.writeln(&format!("{} {} = ({}).clone();", mutability, escaped_name, expr_str));
1662
+ self.writeln(&format!(
1663
+ "{} {} = ({}).clone();",
1664
+ mutability, escaped_name, expr_str
1665
+ ));
1184
1666
  } else {
1185
1667
  self.writeln(&format!("{} {} = {};", mutability, escaped_name, expr_str));
1186
1668
  }
1187
1669
  }
1188
-
1670
+
1189
1671
  if let Some(scope) = self.outer_vars_stack.last_mut() {
1190
1672
  scope.push(name.to_string());
1191
1673
  }
1192
1674
  }
1193
- Statement::VarDeclDestructure { pattern, mutable, init, span, .. } => {
1675
+ Statement::VarDeclDestructure {
1676
+ pattern,
1677
+ mutable,
1678
+ init,
1679
+ span,
1680
+ ..
1681
+ } => {
1194
1682
  let expr = self.emit_expr(init)?;
1195
1683
  let mutability = if *mutable { "let mut" } else { "let" };
1196
- let clone_suffix = if Self::needs_clone(init) { ".clone()" } else { "" };
1684
+ let clone_suffix = if Self::needs_clone(init) {
1685
+ ".clone()"
1686
+ } else {
1687
+ ""
1688
+ };
1197
1689
  self.writeln(&format!("let _destruct_val = ({}){};", expr, clone_suffix));
1198
1690
  self.emit_destruct_bindings(pattern, "_destruct_val", mutability, *span)?;
1691
+ self.register_destruct_pattern_outer_vars(pattern);
1199
1692
  }
1200
1693
  Statement::ExprStmt { expr, .. } => {
1201
1694
  let e = self.emit_expr(expr)?;
@@ -1232,7 +1725,12 @@ impl Codegen {
1232
1725
  self.indent -= 1;
1233
1726
  self.writeln("}");
1234
1727
  }
1235
- Statement::ForOf { name, iterable, body, .. } => {
1728
+ Statement::ForOf {
1729
+ name,
1730
+ iterable,
1731
+ body,
1732
+ ..
1733
+ } => {
1236
1734
  let iter_expr = self.emit_expr(iterable)?;
1237
1735
  self.writeln(&format!("{{ let _fof = ({}).clone();", iter_expr));
1238
1736
  self.indent += 1;
@@ -1242,7 +1740,10 @@ impl Codegen {
1242
1740
  self.indent += 1;
1243
1741
  self.writeln("for _v in _arr.borrow().iter() {");
1244
1742
  self.indent += 1;
1245
- self.writeln(&format!("let {} = _v.clone();", Self::escape_ident(name.as_ref())));
1743
+ self.writeln(&format!(
1744
+ "let {} = _v.clone();",
1745
+ Self::escape_ident(name.as_ref())
1746
+ ));
1246
1747
  self.emit_statement(body)?;
1247
1748
  self.indent -= 1;
1248
1749
  self.writeln("}");
@@ -1320,12 +1821,10 @@ impl Codegen {
1320
1821
  }
1321
1822
  }
1322
1823
  Statement::Continue { .. } => {
1323
- let snippet = self.loop_stack.last().map(|(label, update)| {
1324
- (
1325
- label.clone(),
1326
- update.clone(),
1327
- )
1328
- });
1824
+ let snippet = self
1825
+ .loop_stack
1826
+ .last()
1827
+ .map(|(label, update)| (label.clone(), update.clone()));
1329
1828
  if let Some((label, Some(update))) = snippet {
1330
1829
  self.writeln(&update);
1331
1830
  self.writeln(&format!("continue {};", label));
@@ -1341,7 +1840,15 @@ impl Codegen {
1341
1840
  span: None,
1342
1841
  });
1343
1842
  }
1344
- Statement::Switch { expr, cases, default_body, .. } => {
1843
+ Statement::TypeAlias { .. }
1844
+ | Statement::DeclareVar { .. }
1845
+ | Statement::DeclareFun { .. } => {}
1846
+ Statement::Switch {
1847
+ expr,
1848
+ cases,
1849
+ default_body,
1850
+ ..
1851
+ } => {
1345
1852
  let e = self.emit_expr(expr)?;
1346
1853
  self.writeln(&format!("let _sv = {};", e));
1347
1854
  self.writeln("match () {");
@@ -1389,10 +1896,17 @@ impl Codegen {
1389
1896
  }
1390
1897
  Statement::Throw { value, .. } => {
1391
1898
  let v = self.emit_expr(value)?;
1392
- self.writeln(&format!(
1393
- "return Err(Box::new(tishlang_runtime::TishError::Throw({})) as Box<dyn std::error::Error>);",
1394
- v
1395
- ));
1899
+ if self.value_fn_depth > 0 {
1900
+ self.writeln(&format!(
1901
+ "{{ let _th = {}; panic!(\"uncaught throw: {{}}\", _th.to_display_string()); }}",
1902
+ v
1903
+ ));
1904
+ } else {
1905
+ self.writeln(&format!(
1906
+ "return Err(Box::new(tishlang_runtime::TishError::Throw({})) as Box<dyn std::error::Error>);",
1907
+ v
1908
+ ));
1909
+ }
1396
1910
  }
1397
1911
  Statement::Try {
1398
1912
  body,
@@ -1407,7 +1921,7 @@ impl Codegen {
1407
1921
  self.writeln("Ok(Value::Null)");
1408
1922
  self.indent -= 1;
1409
1923
  self.writeln("})();");
1410
-
1924
+
1411
1925
  if let Some(catch_stmt) = catch_body {
1412
1926
  if let Some(param) = catch_param {
1413
1927
  self.writeln("if let Err(e) = _try_result {");
@@ -1417,12 +1931,27 @@ impl Codegen {
1417
1931
  self.writeln("Ok(tish_err) => {");
1418
1932
  self.indent += 1;
1419
1933
  self.writeln("if let tishlang_runtime::TishError::Throw(v) = *tish_err {");
1420
- self.writeln(&format!("let {} = v.clone();", Self::escape_ident(param.as_ref())));
1934
+ self.writeln(&format!(
1935
+ "let {} = v.clone();",
1936
+ Self::escape_ident(param.as_ref())
1937
+ ));
1421
1938
  self.emit_statement(catch_stmt)?;
1422
- self.writeln("} else { return Err(Box::new(tish_err)); }");
1939
+ if self.value_fn_depth > 0 {
1940
+ self.writeln(
1941
+ "} else { panic!(\"unhandled error in native Tish: {:?}\", *tish_err); }",
1942
+ );
1943
+ } else {
1944
+ self.writeln("} else { return Err(Box::new(tish_err)); }");
1945
+ }
1423
1946
  self.indent -= 1;
1424
1947
  self.writeln("}");
1425
- self.writeln("Err(orig) => return Err(orig),");
1948
+ if self.value_fn_depth > 0 {
1949
+ self.writeln(
1950
+ "Err(orig) => panic!(\"non-Tish error in native Tish: {:?}\", orig),",
1951
+ );
1952
+ } else {
1953
+ self.writeln("Err(orig) => return Err(orig),");
1954
+ }
1426
1955
  self.indent -= 1;
1427
1956
  self.writeln("}");
1428
1957
  self.indent -= 1;
@@ -1434,25 +1963,36 @@ impl Codegen {
1434
1963
  }
1435
1964
  self.writeln("}");
1436
1965
  }
1437
-
1966
+
1438
1967
  if let Some(finally_stmt) = finally_body {
1439
1968
  self.emit_statement(finally_stmt)?;
1440
1969
  }
1441
1970
  }
1442
- Statement::FunDecl { name, params, rest_param, body, span, .. } => {
1971
+ Statement::FunDecl {
1972
+ name,
1973
+ params,
1974
+ rest_param,
1975
+ body,
1976
+ span,
1977
+ ..
1978
+ } => {
1443
1979
  // Use Rc<RefCell<>> pattern to allow recursive function calls
1444
1980
  // The function can reference itself through the cell
1445
1981
  let name_raw = name.as_ref();
1446
1982
  let name_str = Self::escape_ident(name_raw);
1447
1983
  // Check if cell was already created by block prescan
1448
- let cell_exists = self.function_scope_stack
1984
+ let cell_exists = self
1985
+ .function_scope_stack
1449
1986
  .last()
1450
1987
  .map(|scope| scope.contains(&name_raw.to_string()))
1451
1988
  .unwrap_or(false);
1452
1989
  if !cell_exists {
1453
- self.writeln(&format!("let {}_cell: Rc<RefCell<Value>> = Rc::new(RefCell::new(Value::Null));", name_str));
1990
+ self.writeln(&format!(
1991
+ "let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
1992
+ name_str
1993
+ ));
1454
1994
  }
1455
-
1995
+
1456
1996
  // Analyze body to find which identifiers are actually referenced
1457
1997
  let mut referenced = HashSet::new();
1458
1998
  Self::collect_stmt_idents(body, &mut referenced);
@@ -1461,9 +2001,10 @@ impl Codegen {
1461
2001
  .flat_map(|p| p.bound_names())
1462
2002
  .map(|n| n.to_string())
1463
2003
  .collect();
1464
-
2004
+
1465
2005
  // Collect all outer parameters that need to be captured (only those referenced)
1466
- let outer_params: Vec<String> = self.outer_params_stack
2006
+ let outer_params: Vec<String> = self
2007
+ .outer_params_stack
1467
2008
  .iter()
1468
2009
  .flat_map(|p| p.iter().cloned())
1469
2010
  .filter(|name| referenced.contains(name) && !param_names.contains(name))
@@ -1472,11 +2013,34 @@ impl Codegen {
1472
2013
  // Exclude params and variables declared in this function's body (locals)
1473
2014
  let mut local_var_names = HashSet::new();
1474
2015
  Self::collect_local_var_names(body, &mut local_var_names);
1475
- let outer_vars: Vec<String> = self.outer_vars_stack
2016
+ let outer_vars: Vec<String> = self
2017
+ .outer_vars_stack
1476
2018
  .iter()
1477
2019
  .flat_map(|v| v.iter().cloned())
1478
- .filter(|name| referenced.contains(name) && !param_names.contains(name) && !local_var_names.contains(name))
1479
- .filter(|name| !["Boolean", "console", "Math", "JSON", "Date", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"].contains(&name.as_str()))
2020
+ .filter(|name| {
2021
+ referenced.contains(name)
2022
+ && !param_names.contains(name)
2023
+ && !local_var_names.contains(name)
2024
+ })
2025
+ .filter(|name| {
2026
+ ![
2027
+ "Boolean",
2028
+ "console",
2029
+ "Math",
2030
+ "JSON",
2031
+ "Date",
2032
+ "Object",
2033
+ "process",
2034
+ "setTimeout",
2035
+ "clearTimeout",
2036
+ "setInterval",
2037
+ "clearInterval",
2038
+ "Promise",
2039
+ "RegExp",
2040
+ "Polars",
2041
+ ]
2042
+ .contains(&name.as_str())
2043
+ })
1480
2044
  .collect();
1481
2045
 
1482
2046
  // Outer vars that are assigned in the body need RefCell (capture cell, add to refcell_wrapped_vars).
@@ -1499,9 +2063,15 @@ impl Codegen {
1499
2063
  for outer_var in &outer_vars {
1500
2064
  let var_escaped = Self::escape_ident(outer_var);
1501
2065
  if self.rc_cell_storage_contains(outer_var) {
1502
- self.writeln(&format!("let {}_cell = {}.clone();", var_escaped, var_escaped));
2066
+ self.writeln(&format!(
2067
+ "let {}_cell = {}.clone();",
2068
+ var_escaped, var_escaped
2069
+ ));
1503
2070
  } else {
1504
- self.writeln(&format!("let {}_cell = std::rc::Rc::new(RefCell::new({}.clone()));", var_escaped, var_escaped));
2071
+ self.writeln(&format!(
2072
+ "let {}_cell = VmRef::new({}.clone());",
2073
+ var_escaped, var_escaped
2074
+ ));
1505
2075
  }
1506
2076
  }
1507
2077
 
@@ -1510,57 +2080,119 @@ impl Codegen {
1510
2080
  // Clone RefCell for outer vars so closure can capture
1511
2081
  for outer_var in &outer_vars {
1512
2082
  let var_escaped = Self::escape_ident(outer_var);
1513
- self.writeln(&format!("let {}_cell = {}_cell.clone();", var_escaped, var_escaped));
2083
+ self.writeln(&format!(
2084
+ "let {}_cell = {}_cell.clone();",
2085
+ var_escaped, var_escaped
2086
+ ));
1514
2087
  }
1515
2088
  // Clone the cell so the closure can reference the function recursively
1516
2089
  let needs_self_ref = referenced.contains(name_raw);
1517
2090
  if needs_self_ref {
1518
- self.writeln(&format!("let {}_ref = {}_cell.clone();", name_str, name_str));
2091
+ self.writeln(&format!(
2092
+ "let {}_ref = {}_cell.clone();",
2093
+ name_str, name_str
2094
+ ));
1519
2095
  }
1520
2096
  // Clone sibling function cells for mutual recursion
1521
- let sibling_fns: Vec<String> = self.function_scope_stack
2097
+ let sibling_fns: Vec<String> = self
2098
+ .function_scope_stack
1522
2099
  .last()
1523
- .map(|scope| scope.iter()
1524
- .filter(|s| s.as_str() != name_raw && referenced.contains(s.as_str()))
1525
- .cloned()
1526
- .collect())
2100
+ .map(|scope| {
2101
+ scope
2102
+ .iter()
2103
+ .filter(|s| s.as_str() != name_raw && referenced.contains(s.as_str()))
2104
+ .cloned()
2105
+ .collect()
2106
+ })
1527
2107
  .unwrap_or_default();
1528
2108
  for sibling in &sibling_fns {
1529
2109
  let sibling_escaped = Self::escape_ident(sibling);
1530
- self.writeln(&format!("let {}_ref = {}_cell.clone();", sibling_escaped, sibling_escaped));
2110
+ self.writeln(&format!(
2111
+ "let {}_ref = {}_cell.clone();",
2112
+ sibling_escaped, sibling_escaped
2113
+ ));
1531
2114
  }
1532
2115
  // Clone outer parameters so they can be captured by the move closure
1533
2116
  for outer_param in &outer_params {
1534
2117
  let param_escaped = Self::escape_ident(outer_param);
1535
- self.writeln(&format!("let {} = {}.clone();", param_escaped, param_escaped));
2118
+ self.writeln(&format!(
2119
+ "let {} = {}.clone();",
2120
+ param_escaped, param_escaped
2121
+ ));
1536
2122
  }
1537
2123
  // Only clone builtins that are actually referenced (clone so outer scope can still use them, e.g. process for PORT before serve)
1538
- for builtin in &["Boolean", "console", "Math", "JSON", "Date", "Uint8Array", "AudioContext", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"] {
2124
+ for builtin in &[
2125
+ "Boolean",
2126
+ "console",
2127
+ "Math",
2128
+ "JSON",
2129
+ "Date",
2130
+ "Object",
2131
+ "Uint8Array",
2132
+ "AudioContext",
2133
+ "process",
2134
+ "setTimeout",
2135
+ "clearTimeout",
2136
+ "setInterval",
2137
+ "clearInterval",
2138
+ "Promise",
2139
+ "RegExp",
2140
+ "Polars",
2141
+ // Free-standing global functions used inside user-defined
2142
+ // functions also need to be cloned into the closure
2143
+ // capture, or the emitted Rust hits E0382 (moved value)
2144
+ // at the closure's defining `let`.
2145
+ "parseInt",
2146
+ "parseFloat",
2147
+ "isNaN",
2148
+ "isFinite",
2149
+ "encodeURI",
2150
+ "decodeURI",
2151
+ "htmlEscape",
2152
+ "registerStaticRoute",
2153
+ "String",
2154
+ "Infinity",
2155
+ "NaN",
2156
+ "serve",
2157
+ ] {
1539
2158
  if referenced.contains(*builtin) {
1540
2159
  self.writeln(&format!("let {} = {}.clone();", builtin, builtin));
1541
2160
  }
1542
2161
  }
1543
- self.writeln("Value::Function(Rc::new(move |args: &[Value]| {");
2162
+ self.writeln("Value::native(move |args: &[Value]| {");
2163
+ self.value_fn_depth += 1;
1544
2164
  self.indent += 1;
1545
2165
  // Mutable outer vars: capture the RefCell so assignments use borrow_mut
1546
2166
  for outer_var in &mutable_outer_vars {
1547
2167
  let var_escaped = Self::escape_ident(outer_var);
1548
- self.writeln(&format!("let {} = {}_cell.clone();", var_escaped, var_escaped));
2168
+ self.writeln(&format!(
2169
+ "let {} = {}_cell.clone();",
2170
+ var_escaped, var_escaped
2171
+ ));
1549
2172
  }
1550
2173
  // Read-only outer vars: Value binding from borrow (avoids param-shadow issues)
1551
2174
  for outer_var in &read_only_outer_vars {
1552
2175
  let var_escaped = Self::escape_ident(outer_var);
1553
- self.writeln(&format!("let {} = (*{}_cell.borrow()).clone();", var_escaped, var_escaped));
2176
+ self.writeln(&format!(
2177
+ "let {} = (*{}_cell.borrow()).clone();",
2178
+ var_escaped, var_escaped
2179
+ ));
1554
2180
  }
1555
2181
  // Make the function available by its name inside the closure (only if recursive)
1556
2182
  if needs_self_ref {
1557
- self.writeln(&format!("let {} = (*{}_ref.borrow()).clone();", name_str, name_str));
2183
+ self.writeln(&format!(
2184
+ "let {} = (*{}_ref.borrow()).clone();",
2185
+ name_str, name_str
2186
+ ));
1558
2187
  }
1559
2188
  // Make sibling functions available for mutual recursion
1560
2189
  for sibling in &sibling_fns {
1561
2190
  let sibling_escaped = Self::escape_ident(sibling);
1562
- self.writeln(&format!("let {} = (*{}_ref.borrow()).clone();", sibling_escaped, sibling_escaped));
1563
- }
2191
+ self.writeln(&format!(
2192
+ "let {} = (*{}_ref.borrow()).clone();",
2193
+ sibling_escaped, sibling_escaped
2194
+ ));
2195
+ }
1564
2196
  // Extract just the parameter names (type annotations are parsed but not used in codegen yet)
1565
2197
  let current_param_names: Vec<String> = params
1566
2198
  .iter()
@@ -1589,58 +2221,88 @@ impl Codegen {
1589
2221
  }
1590
2222
  if let Some(rest) = rest_param {
1591
2223
  self.writeln(&format!(
1592
- "let {} = Value::Array(std::rc::Rc::new(RefCell::new(args[{}..].to_vec())));",
2224
+ "let {} = Value::Array(VmRef::new(args[{}..].to_vec()));",
1593
2225
  Self::escape_ident(rest.name.as_ref()),
1594
2226
  params.len()
1595
2227
  ));
1596
2228
  }
1597
-
1598
- // Push current params to stack for nested functions
1599
- self.outer_params_stack.push(current_param_names);
1600
-
1601
- // Function bodies are sync closures (even Tish async fn) - use block_on for await
1602
- self.async_context_stack.push(false);
1603
2229
 
1604
- // Mutable outer vars must be in refcell_wrapped_vars so Assign/CompoundAssign emit borrow_mut
1605
- let saved_refcell = self.refcell_wrapped_vars.clone();
1606
- for v in &mutable_outer_vars {
1607
- self.refcell_wrapped_vars.insert(v.clone());
1608
- }
1609
-
1610
- // Pre-scan body for nested functions (handles function body as Block)
1611
- if let Statement::Block { statements, .. } = body.as_ref() {
1612
- let nested_func_names = self.prescan_function_decls(statements);
1613
- self.function_scope_stack.push(nested_func_names.clone());
1614
- // Create cells for nested functions
1615
- for func_name in &nested_func_names {
1616
- let escaped = Self::escape_ident(func_name);
1617
- self.writeln(&format!("let {}_cell: Rc<RefCell<Value>> = Rc::new(RefCell::new(Value::Null));", escaped));
1618
- }
1619
- for s in statements {
1620
- self.emit_statement(s)?;
2230
+ self.type_context
2231
+ .push_fun_param_scope(params, rest_param.as_ref());
2232
+
2233
+ let fun_body_res: Result<(), CompileError> = (|| -> Result<(), CompileError> {
2234
+ // Push current params to stack for nested functions
2235
+ self.outer_params_stack.push(current_param_names);
2236
+
2237
+ // Function bodies are sync closures (even Tish async fn) - use block_on for await
2238
+ self.async_context_stack.push(false);
2239
+
2240
+ // Mutable outer vars must be in refcell_wrapped_vars so Assign/CompoundAssign emit borrow_mut
2241
+ let saved_refcell = self.refcell_wrapped_vars.clone();
2242
+ for v in &mutable_outer_vars {
2243
+ self.refcell_wrapped_vars.insert(v.clone());
1621
2244
  }
1622
- self.function_scope_stack.pop();
1623
- } else {
1624
- self.function_scope_stack.push(Vec::new());
1625
- self.emit_statement(body)?;
1626
- self.function_scope_stack.pop();
2245
+
2246
+ // Pre-scan body for nested functions (handles function body as Block)
2247
+ if let Statement::Block { statements, .. } = body.as_ref() {
2248
+ let nested_func_names = self.prescan_function_decls(statements);
2249
+ self.function_scope_stack.push(nested_func_names.clone());
2250
+ self.outer_vars_stack.push(Vec::new());
2251
+ self.rc_cell_storage_scopes
2252
+ .push(std::collections::HashSet::new());
2253
+ // Create cells for nested functions
2254
+ for func_name in &nested_func_names {
2255
+ let escaped = Self::escape_ident(func_name);
2256
+ self.writeln(&format!(
2257
+ "let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
2258
+ escaped
2259
+ ));
2260
+ }
2261
+ for s in statements {
2262
+ self.emit_statement(s)?;
2263
+ }
2264
+ self.function_scope_stack.pop();
2265
+ self.outer_vars_stack.pop();
2266
+ self.rc_cell_storage_scopes.pop();
2267
+ } else {
2268
+ self.function_scope_stack.push(Vec::new());
2269
+ self.outer_vars_stack.push(Vec::new());
2270
+ self.rc_cell_storage_scopes
2271
+ .push(std::collections::HashSet::new());
2272
+ self.emit_statement(body)?;
2273
+ self.function_scope_stack.pop();
2274
+ self.outer_vars_stack.pop();
2275
+ self.rc_cell_storage_scopes.pop();
2276
+ }
2277
+
2278
+ self.async_context_stack.pop();
2279
+
2280
+ // Restore refcell_wrapped_vars (remove mutable outer vars we added)
2281
+ self.refcell_wrapped_vars = saved_refcell;
2282
+
2283
+ // Pop params stack
2284
+ self.outer_params_stack.pop();
2285
+
2286
+ Ok(())
2287
+ })();
2288
+
2289
+ self.type_context.pop_scope();
2290
+ if let Err(e) = fun_body_res {
2291
+ self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
2292
+ return Err(e);
1627
2293
  }
1628
-
1629
- self.async_context_stack.pop();
1630
2294
 
1631
- // Restore refcell_wrapped_vars (remove mutable outer vars we added)
1632
- self.refcell_wrapped_vars = saved_refcell;
1633
-
1634
- // Pop params stack
1635
- self.outer_params_stack.pop();
1636
-
1637
2295
  self.writeln("Value::Null");
1638
2296
  self.indent -= 1;
1639
- self.writeln("}))");
2297
+ self.writeln("})");
2298
+ self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
1640
2299
  self.indent -= 1;
1641
2300
  self.writeln("};");
1642
2301
  // Update the cell with the actual function value
1643
- self.writeln(&format!("*{}_cell.borrow_mut() = {}.clone();", name_str, name_str));
2302
+ self.writeln(&format!(
2303
+ "*{}_cell.borrow_mut() = {}.clone();",
2304
+ name_str, name_str
2305
+ ));
1644
2306
  }
1645
2307
  }
1646
2308
  Ok(())
@@ -1673,7 +2335,10 @@ impl Codegen {
1673
2335
  }
1674
2336
  }
1675
2337
  }
1676
- Ok(format!("{{ let mut _args: Vec<Value> = Vec::new(); {} _args }}", parts.join(" ")))
2338
+ Ok(format!(
2339
+ "{{ let mut _args: Vec<Value> = Vec::new(); {} _args }}",
2340
+ parts.join(" ")
2341
+ ))
1677
2342
  } else {
1678
2343
  let mut emitted = Vec::new();
1679
2344
  for arg in args {
@@ -1695,14 +2360,20 @@ impl Codegen {
1695
2360
  }
1696
2361
  }
1697
2362
 
1698
- fn emit_destruct_bindings(&mut self, pattern: &DestructPattern, value_expr: &str, mutability: &str, span: Span) -> Result<(), CompileError> {
2363
+ fn emit_destruct_bindings(
2364
+ &mut self,
2365
+ pattern: &DestructPattern,
2366
+ value_expr: &str,
2367
+ mutability: &str,
2368
+ span: Span,
2369
+ ) -> Result<(), CompileError> {
1699
2370
  // Flat `let` bindings so names stay in scope for the rest of the function (e.g. JSX).
1700
2371
  match pattern {
1701
2372
  DestructPattern::Array(elements) => {
1702
2373
  for (i, elem) in elements.iter().enumerate() {
1703
2374
  if let Some(el) = elem {
1704
2375
  match el {
1705
- DestructElement::Ident(name) => {
2376
+ DestructElement::Ident(name, _) => {
1706
2377
  self.writeln(&format!(
1707
2378
  "{} {} = match &({}) {{ Value::Array(ref _a) => _a.borrow().get({}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
1708
2379
  mutability,
@@ -1719,9 +2390,9 @@ impl Codegen {
1719
2390
  ));
1720
2391
  self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
1721
2392
  }
1722
- DestructElement::Rest(name) => {
2393
+ DestructElement::Rest(name, _) => {
1723
2394
  self.writeln(&format!(
1724
- "{} {} = match &({}) {{ Value::Array(ref _a) => {{ let _b = _a.borrow(); Value::Array(Rc::new(RefCell::new(_b.iter().skip({}).cloned().collect()))) }}, _ => Value::Array(Rc::new(RefCell::new(Vec::new()))) }};",
2395
+ "{} {} = match &({}) {{ Value::Array(ref _a) => {{ let _b = _a.borrow(); Value::Array(VmRef::new(_b.iter().skip({}).cloned().collect())) }}, _ => Value::Array(VmRef::new(Vec::new())) }};",
1725
2396
  mutability,
1726
2397
  Self::escape_ident(name.as_ref()),
1727
2398
  value_expr,
@@ -1736,7 +2407,7 @@ impl Codegen {
1736
2407
  for prop in props {
1737
2408
  let key = prop.key.as_ref();
1738
2409
  match &prop.value {
1739
- DestructElement::Ident(name) => {
2410
+ DestructElement::Ident(name, _) => {
1740
2411
  self.writeln(&format!(
1741
2412
  "{} {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
1742
2413
  mutability,
@@ -1753,7 +2424,7 @@ impl Codegen {
1753
2424
  ));
1754
2425
  self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
1755
2426
  }
1756
- DestructElement::Rest(_) => {
2427
+ DestructElement::Rest(_, _) => {
1757
2428
  return Err(CompileError::new(
1758
2429
  "Rest in object destructuring not supported",
1759
2430
  Some(span),
@@ -1766,6 +2437,47 @@ impl Codegen {
1766
2437
  Ok(())
1767
2438
  }
1768
2439
 
2440
+ /// Like `VarDecl` pushing onto `outer_vars_stack`, so nested `move` closures rebind
2441
+ /// destructured names via `_cell` / `.clone()` instead of moving `Value` multiple times.
2442
+ fn register_destruct_pattern_outer_vars(&mut self, pattern: &DestructPattern) {
2443
+ match pattern {
2444
+ DestructPattern::Array(elements) => {
2445
+ for el in elements.iter().flatten() {
2446
+ match el {
2447
+ DestructElement::Ident(name, _) => {
2448
+ if let Some(scope) = self.outer_vars_stack.last_mut() {
2449
+ scope.push(name.to_string());
2450
+ }
2451
+ }
2452
+ DestructElement::Pattern(nested) => {
2453
+ self.register_destruct_pattern_outer_vars(nested);
2454
+ }
2455
+ DestructElement::Rest(name, _) => {
2456
+ if let Some(scope) = self.outer_vars_stack.last_mut() {
2457
+ scope.push(name.to_string());
2458
+ }
2459
+ }
2460
+ }
2461
+ }
2462
+ }
2463
+ DestructPattern::Object(props) => {
2464
+ for prop in props {
2465
+ match &prop.value {
2466
+ DestructElement::Ident(name, _) => {
2467
+ if let Some(scope) = self.outer_vars_stack.last_mut() {
2468
+ scope.push(name.to_string());
2469
+ }
2470
+ }
2471
+ DestructElement::Pattern(nested) => {
2472
+ self.register_destruct_pattern_outer_vars(nested);
2473
+ }
2474
+ DestructElement::Rest(_, _) => {}
2475
+ }
2476
+ }
2477
+ }
2478
+ }
2479
+ }
2480
+
1769
2481
  fn emit_expr(&mut self, expr: &Expr) -> Result<String, CompileError> {
1770
2482
  Ok(match expr {
1771
2483
  Expr::Literal { value, .. } => match value {
@@ -1819,6 +2531,51 @@ impl Codegen {
1819
2531
  }
1820
2532
  }
1821
2533
  Expr::Call { callee, args, .. } => {
2534
+ // Typed-struct shortcut for `JSON.stringify(typedValue)`.
2535
+ // When the single arg has a known native type that owns a
2536
+ // hand-rolled `_tish_write_json` (struct or `Vec<struct>`),
2537
+ // emit a direct write into a String buffer and skip the
2538
+ // entire `Value::Object` / `Value::Array` allocation
2539
+ // round-trip + the dynamic stringifier walk. Wraps the
2540
+ // result in `Value::String` for the caller, which is what
2541
+ // the existing `JSON.stringify` returned anyway.
2542
+ if let Expr::Member {
2543
+ object,
2544
+ prop: MemberProp::Name { name: method_name, .. },
2545
+ ..
2546
+ } = callee.as_ref()
2547
+ {
2548
+ if method_name.as_ref() == "stringify"
2549
+ && matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "JSON")
2550
+ {
2551
+ if args.len() == 1 {
2552
+ if let CallArg::Expr(arg) = &args[0] {
2553
+ let (arg_code, arg_ty) = self.emit_typed_expr(arg)?;
2554
+ match &arg_ty {
2555
+ crate::types::RustType::Named { .. } => {
2556
+ return Ok(format!(
2557
+ "{{ let mut _buf = String::with_capacity(128); ({})._tish_write_json(&mut _buf); Value::String(_buf.into()) }}",
2558
+ arg_code
2559
+ ));
2560
+ }
2561
+ crate::types::RustType::Vec(inner)
2562
+ if matches!(
2563
+ inner.as_ref(),
2564
+ crate::types::RustType::Named { .. }
2565
+ ) =>
2566
+ {
2567
+ return Ok(format!(
2568
+ "{{ let mut _buf = String::with_capacity(256); _buf.push('['); for (i, item) in ({}).iter().enumerate() {{ if i > 0 {{ _buf.push(','); }} item._tish_write_json(&mut _buf); }} _buf.push(']'); Value::String(_buf.into()) }}",
2569
+ arg_code
2570
+ ));
2571
+ }
2572
+ _ => {}
2573
+ }
2574
+ }
2575
+ }
2576
+ }
2577
+ }
2578
+
1822
2579
  // Compile-time embed: Polars.read_csv("<literal path>") when file exists
1823
2580
  if let Some(init) = self.native_module_init.get("tish:polars") {
1824
2581
  let crate_name = match init {
@@ -1834,7 +2591,7 @@ impl Codegen {
1834
2591
  {
1835
2592
  if let Expr::Member {
1836
2593
  object,
1837
- prop: MemberProp::Name(ref method_name),
2594
+ prop: MemberProp::Name { name: ref method_name, .. },
1838
2595
  ..
1839
2596
  } = callee.as_ref()
1840
2597
  {
@@ -1865,7 +2622,12 @@ impl Codegen {
1865
2622
  }
1866
2623
 
1867
2624
  // Check for built-in method calls on arrays/strings
1868
- if let Expr::Member { object, prop: MemberProp::Name(method_name), .. } = callee.as_ref() {
2625
+ if let Expr::Member {
2626
+ object,
2627
+ prop: MemberProp::Name { name: method_name, .. },
2628
+ ..
2629
+ } = callee.as_ref()
2630
+ {
1869
2631
  // ── native Vec<T> push fast path ──────────────────────────────
1870
2632
  if method_name.as_ref() == "push" {
1871
2633
  if let Expr::Ident { name, .. } = object.as_ref() {
@@ -2248,9 +3010,43 @@ impl Codegen {
2248
3010
  optional,
2249
3011
  ..
2250
3012
  } => {
3013
+ // Fast path: typed struct member access. If `object` is
3014
+ // a local with `RustType::Named { fields }` and `prop` is
3015
+ // a literal field name of that struct, lower to a direct
3016
+ // Rust field access (`obj.field`), then wrap in
3017
+ // `Value::*` so the caller gets a `Value` as expected.
3018
+ if !optional {
3019
+ if let (Expr::Ident { name: var_name, .. }, MemberProp::Name { name: prop_name, .. }) =
3020
+ (object.as_ref(), prop)
3021
+ {
3022
+ let var_type = self.type_context.get_type(var_name.as_ref());
3023
+ if let RustType::Named { fields, .. } = &var_type {
3024
+ if let Some((_, field_ty)) =
3025
+ fields.iter().find(|(k, _)| k.as_ref() == prop_name.as_ref())
3026
+ {
3027
+ let var_esc = Self::escape_ident(var_name.as_ref()).into_owned();
3028
+ let access = if self.refcell_wrapped_vars.contains(var_name.as_ref()) {
3029
+ format!(
3030
+ "(*{}.borrow()).{}.clone()",
3031
+ var_esc,
3032
+ crate::types::field_ident(prop_name.as_ref())
3033
+ )
3034
+ } else {
3035
+ format!(
3036
+ "{}.{}",
3037
+ var_esc,
3038
+ crate::types::field_ident(prop_name.as_ref())
3039
+ )
3040
+ };
3041
+ // Caller expects a `Value`; wrap.
3042
+ return Ok(field_ty.to_value_expr(&access));
3043
+ }
3044
+ }
3045
+ }
3046
+ }
2251
3047
  let obj = self.emit_expr(object)?;
2252
3048
  let key = match prop {
2253
- MemberProp::Name(n) => format!("{:?}", n.as_ref()),
3049
+ MemberProp::Name { name, .. } => format!("{:?}", name.as_ref()),
2254
3050
  MemberProp::Expr(e) => {
2255
3051
  let k = self.emit_expr(e)?;
2256
3052
  format!("{}.to_display_string()", k)
@@ -2324,7 +3120,7 @@ impl Codegen {
2324
3120
  }
2325
3121
  }
2326
3122
  }
2327
- format!("{{ let mut _arr: Vec<Value> = Vec::new(); {} Value::Array(Rc::new(RefCell::new(_arr))) }}", parts.join(" "))
3123
+ format!("{{ let mut _arr: Vec<Value> = Vec::new(); {} Value::Array(VmRef::new(_arr)) }}", parts.join(" "))
2328
3124
  } else {
2329
3125
  let mut els = Vec::new();
2330
3126
  for elem in elements {
@@ -2343,7 +3139,7 @@ impl Codegen {
2343
3139
  }
2344
3140
  }
2345
3141
  format!(
2346
- "Value::Array(Rc::new(RefCell::new(vec![{}])))",
3142
+ "Value::Array(VmRef::new(vec![{}]))",
2347
3143
  els.join(", ")
2348
3144
  )
2349
3145
  }
@@ -2368,7 +3164,7 @@ impl Codegen {
2368
3164
  }
2369
3165
  }
2370
3166
  }
2371
- format!("{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::Object(Rc::new(RefCell::new(_obj))) }}", parts.join(" "))
3167
+ format!("{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::Object(VmRef::new(_obj)) }}", parts.join(" "))
2372
3168
  } else {
2373
3169
  let mut parts = Vec::new();
2374
3170
  for prop in props {
@@ -2382,7 +3178,7 @@ impl Codegen {
2382
3178
  }
2383
3179
  }
2384
3180
  format!(
2385
- "Value::Object(Rc::new(RefCell::new(ObjectMap::from([{}]))))",
3181
+ "Value::Object(VmRef::new(ObjectMap::from([{}])))",
2386
3182
  parts.join(", ")
2387
3183
  )
2388
3184
  }
@@ -2803,9 +3599,12 @@ impl Codegen {
2803
3599
  format!("Value::String([{}].concat().into())", parts.join(", "))
2804
3600
  }
2805
3601
  Expr::JsxElement { .. } | Expr::JsxFragment { .. } => {
2806
- tishlang_ui::jsx::emit_jsx_rust(expr, &mut |e| {
2807
- self.emit_expr(e).map_err(|ce| ce.message)
2808
- })
3602
+ let fun_decls = self.program_fun_decl_names.clone();
3603
+ tishlang_ui::jsx::emit_jsx_rust(
3604
+ expr,
3605
+ &mut |e| self.emit_expr(e).map_err(|ce| ce.message),
3606
+ &fun_decls,
3607
+ )
2809
3608
  .map_err(|m| CompileError::new(m, None))?
2810
3609
  }
2811
3610
  Expr::New { callee, args, .. } => {
@@ -2852,7 +3651,7 @@ impl Codegen {
2852
3651
  }
2853
3652
  })
2854
3653
  }
2855
-
3654
+
2856
3655
  /// Collect all identifiers referenced in an arrow body
2857
3656
  fn collect_referenced_idents(body: &ArrowBody) -> HashSet<String> {
2858
3657
  let mut idents = HashSet::new();
@@ -2862,10 +3661,12 @@ impl Codegen {
2862
3661
  }
2863
3662
  idents
2864
3663
  }
2865
-
3664
+
2866
3665
  fn collect_expr_idents(expr: &Expr, idents: &mut HashSet<String>) {
2867
3666
  match expr {
2868
- Expr::Ident { name, .. } => { idents.insert(name.to_string()); }
3667
+ Expr::Ident { name, .. } => {
3668
+ idents.insert(name.to_string());
3669
+ }
2869
3670
  Expr::Assign { name, value, .. } => {
2870
3671
  idents.insert(name.to_string());
2871
3672
  Self::collect_expr_idents(value, idents);
@@ -2879,7 +3680,9 @@ impl Codegen {
2879
3680
  Self::collect_expr_idents(callee, idents);
2880
3681
  for arg in args {
2881
3682
  match arg {
2882
- CallArg::Expr(e) | CallArg::Spread(e) => Self::collect_expr_idents(e, idents),
3683
+ CallArg::Expr(e) | CallArg::Spread(e) => {
3684
+ Self::collect_expr_idents(e, idents)
3685
+ }
2883
3686
  }
2884
3687
  }
2885
3688
  }
@@ -2887,19 +3690,28 @@ impl Codegen {
2887
3690
  Self::collect_expr_idents(callee, idents);
2888
3691
  for arg in args {
2889
3692
  match arg {
2890
- CallArg::Expr(e) | CallArg::Spread(e) => Self::collect_expr_idents(e, idents),
3693
+ CallArg::Expr(e) | CallArg::Spread(e) => {
3694
+ Self::collect_expr_idents(e, idents)
3695
+ }
2891
3696
  }
2892
3697
  }
2893
3698
  }
2894
3699
  Expr::Member { object, prop, .. } => {
2895
3700
  Self::collect_expr_idents(object, idents);
2896
- if let MemberProp::Expr(e) = prop { Self::collect_expr_idents(e, idents); }
3701
+ if let MemberProp::Expr(e) = prop {
3702
+ Self::collect_expr_idents(e, idents);
3703
+ }
2897
3704
  }
2898
3705
  Expr::MemberAssign { object, value, .. } => {
2899
3706
  Self::collect_expr_idents(object, idents);
2900
3707
  Self::collect_expr_idents(value, idents);
2901
3708
  }
2902
- Expr::IndexAssign { object, index, value, .. } => {
3709
+ Expr::IndexAssign {
3710
+ object,
3711
+ index,
3712
+ value,
3713
+ ..
3714
+ } => {
2903
3715
  Self::collect_expr_idents(object, idents);
2904
3716
  Self::collect_expr_idents(index, idents);
2905
3717
  Self::collect_expr_idents(value, idents);
@@ -2908,13 +3720,20 @@ impl Codegen {
2908
3720
  Self::collect_expr_idents(object, idents);
2909
3721
  Self::collect_expr_idents(index, idents);
2910
3722
  }
2911
- Expr::Conditional { cond, then_branch, else_branch, .. } => {
3723
+ Expr::Conditional {
3724
+ cond,
3725
+ then_branch,
3726
+ else_branch,
3727
+ ..
3728
+ } => {
2912
3729
  Self::collect_expr_idents(cond, idents);
2913
3730
  Self::collect_expr_idents(then_branch, idents);
2914
3731
  Self::collect_expr_idents(else_branch, idents);
2915
3732
  }
2916
- Expr::PostfixInc { name, .. } | Expr::PostfixDec { name, .. } |
2917
- Expr::PrefixInc { name, .. } | Expr::PrefixDec { name, .. } => {
3733
+ Expr::PostfixInc { name, .. }
3734
+ | Expr::PostfixDec { name, .. }
3735
+ | Expr::PrefixInc { name, .. }
3736
+ | Expr::PrefixDec { name, .. } => {
2918
3737
  idents.insert(name.to_string());
2919
3738
  }
2920
3739
  Expr::CompoundAssign { name, value, .. } => {
@@ -2928,23 +3747,25 @@ impl Codegen {
2928
3747
  Expr::Array { elements, .. } => {
2929
3748
  for el in elements {
2930
3749
  match el {
2931
- ArrayElement::Expr(e) | ArrayElement::Spread(e) => Self::collect_expr_idents(e, idents),
3750
+ ArrayElement::Expr(e) | ArrayElement::Spread(e) => {
3751
+ Self::collect_expr_idents(e, idents)
3752
+ }
2932
3753
  }
2933
3754
  }
2934
3755
  }
2935
3756
  Expr::Object { props, .. } => {
2936
3757
  for prop in props {
2937
3758
  match prop {
2938
- ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => Self::collect_expr_idents(e, idents),
3759
+ ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => {
3760
+ Self::collect_expr_idents(e, idents)
3761
+ }
2939
3762
  }
2940
3763
  }
2941
3764
  }
2942
- Expr::ArrowFunction { body, .. } => {
2943
- match body {
2944
- ArrowBody::Expr(e) => Self::collect_expr_idents(e, idents),
2945
- ArrowBody::Block(s) => Self::collect_stmt_idents(s, idents),
2946
- }
2947
- }
3765
+ Expr::ArrowFunction { body, .. } => match body {
3766
+ ArrowBody::Expr(e) => Self::collect_expr_idents(e, idents),
3767
+ ArrowBody::Block(s) => Self::collect_stmt_idents(s, idents),
3768
+ },
2948
3769
  Expr::NullishCoalesce { left, right, .. } => {
2949
3770
  Self::collect_expr_idents(left, idents);
2950
3771
  Self::collect_expr_idents(right, idents);
@@ -2952,12 +3773,20 @@ impl Codegen {
2952
3773
  Expr::TypeOf { operand, .. } => Self::collect_expr_idents(operand, idents),
2953
3774
  Expr::Await { operand, .. } => Self::collect_expr_idents(operand, idents),
2954
3775
  Expr::TemplateLiteral { exprs, .. } => {
2955
- for e in exprs { Self::collect_expr_idents(e, idents); }
3776
+ for e in exprs {
3777
+ Self::collect_expr_idents(e, idents);
3778
+ }
2956
3779
  }
2957
- Expr::JsxElement { props, children, .. } => {
3780
+ Expr::JsxElement {
3781
+ props, children, ..
3782
+ } => {
2958
3783
  for p in props {
2959
3784
  match p {
2960
- tishlang_ast::JsxProp::Attr { value: tishlang_ast::JsxAttrValue::Expr(e), .. } | tishlang_ast::JsxProp::Spread(e) => Self::collect_expr_idents(e, idents),
3785
+ tishlang_ast::JsxProp::Attr {
3786
+ value: tishlang_ast::JsxAttrValue::Expr(e),
3787
+ ..
3788
+ }
3789
+ | tishlang_ast::JsxProp::Spread(e) => Self::collect_expr_idents(e, idents),
2961
3790
  _ => {}
2962
3791
  }
2963
3792
  }
@@ -2978,7 +3807,7 @@ impl Codegen {
2978
3807
  Expr::Literal { .. } => {}
2979
3808
  }
2980
3809
  }
2981
-
3810
+
2982
3811
  /// Collect variable names that are assigned to in a statement/body (target of =, +=, ++, etc).
2983
3812
  fn collect_assigned_idents_in_stmt(stmt: &Statement, names: &mut HashSet<String>) {
2984
3813
  match stmt {
@@ -2989,14 +3818,25 @@ impl Codegen {
2989
3818
  Self::collect_assigned_idents_in_stmt(s, names);
2990
3819
  }
2991
3820
  }
2992
- Statement::If { cond, then_branch, else_branch, .. } => {
3821
+ Statement::If {
3822
+ cond,
3823
+ then_branch,
3824
+ else_branch,
3825
+ ..
3826
+ } => {
2993
3827
  Self::collect_assigned_idents_in_expr(cond, names);
2994
3828
  Self::collect_assigned_idents_in_stmt(then_branch, names);
2995
3829
  if let Some(eb) = else_branch {
2996
3830
  Self::collect_assigned_idents_in_stmt(eb, names);
2997
3831
  }
2998
3832
  }
2999
- Statement::For { init, cond, update, body, .. } => {
3833
+ Statement::For {
3834
+ init,
3835
+ cond,
3836
+ update,
3837
+ body,
3838
+ ..
3839
+ } => {
3000
3840
  if let Some(i) = init {
3001
3841
  Self::collect_assigned_idents_in_stmt(i, names);
3002
3842
  }
@@ -3016,7 +3856,12 @@ impl Codegen {
3016
3856
  Self::collect_assigned_idents_in_expr(cond, names);
3017
3857
  Self::collect_assigned_idents_in_stmt(body, names);
3018
3858
  }
3019
- Statement::Switch { expr, cases, default_body, .. } => {
3859
+ Statement::Switch {
3860
+ expr,
3861
+ cases,
3862
+ default_body,
3863
+ ..
3864
+ } => {
3020
3865
  Self::collect_assigned_idents_in_expr(expr, names);
3021
3866
  for (case_expr, stmts) in cases {
3022
3867
  if let Some(e) = case_expr {
@@ -3032,7 +3877,12 @@ impl Codegen {
3032
3877
  }
3033
3878
  }
3034
3879
  }
3035
- Statement::Try { body, catch_body, finally_body, .. } => {
3880
+ Statement::Try {
3881
+ body,
3882
+ catch_body,
3883
+ finally_body,
3884
+ ..
3885
+ } => {
3036
3886
  Self::collect_assigned_idents_in_stmt(body, names);
3037
3887
  if let Some(c) = catch_body {
3038
3888
  Self::collect_assigned_idents_in_stmt(c, names);
@@ -3048,7 +3898,13 @@ impl Codegen {
3048
3898
  }
3049
3899
  }
3050
3900
  Statement::Throw { value, .. } => Self::collect_assigned_idents_in_expr(value, names),
3051
- Statement::Break { .. } | Statement::Continue { .. } | Statement::Import { .. } | Statement::Export { .. } => {}
3901
+ Statement::Break { .. }
3902
+ | Statement::Continue { .. }
3903
+ | Statement::Import { .. }
3904
+ | Statement::Export { .. }
3905
+ | Statement::TypeAlias { .. }
3906
+ | Statement::DeclareVar { .. }
3907
+ | Statement::DeclareFun { .. } => {}
3052
3908
  }
3053
3909
  }
3054
3910
 
@@ -3066,15 +3922,22 @@ impl Codegen {
3066
3922
  names.insert(name.to_string());
3067
3923
  Self::collect_assigned_idents_in_expr(value, names);
3068
3924
  }
3069
- Expr::PostfixInc { name, .. } | Expr::PostfixDec { name, .. }
3070
- | Expr::PrefixInc { name, .. } | Expr::PrefixDec { name, .. } => {
3925
+ Expr::PostfixInc { name, .. }
3926
+ | Expr::PostfixDec { name, .. }
3927
+ | Expr::PrefixInc { name, .. }
3928
+ | Expr::PrefixDec { name, .. } => {
3071
3929
  names.insert(name.to_string());
3072
3930
  }
3073
3931
  Expr::MemberAssign { object, value, .. } => {
3074
3932
  Self::collect_assigned_idents_in_expr(object, names);
3075
3933
  Self::collect_assigned_idents_in_expr(value, names);
3076
3934
  }
3077
- Expr::IndexAssign { object, index, value, .. } => {
3935
+ Expr::IndexAssign {
3936
+ object,
3937
+ index,
3938
+ value,
3939
+ ..
3940
+ } => {
3078
3941
  Self::collect_assigned_idents_in_expr(object, names);
3079
3942
  Self::collect_assigned_idents_in_expr(index, names);
3080
3943
  Self::collect_assigned_idents_in_expr(value, names);
@@ -3114,17 +3977,20 @@ impl Codegen {
3114
3977
  Self::collect_assigned_idents_in_expr(object, names);
3115
3978
  Self::collect_assigned_idents_in_expr(index, names);
3116
3979
  }
3117
- Expr::Conditional { cond, then_branch, else_branch, .. } => {
3980
+ Expr::Conditional {
3981
+ cond,
3982
+ then_branch,
3983
+ else_branch,
3984
+ ..
3985
+ } => {
3118
3986
  Self::collect_assigned_idents_in_expr(cond, names);
3119
3987
  Self::collect_assigned_idents_in_expr(then_branch, names);
3120
3988
  Self::collect_assigned_idents_in_expr(else_branch, names);
3121
3989
  }
3122
- Expr::ArrowFunction { body, .. } => {
3123
- match body {
3124
- ArrowBody::Expr(e) => Self::collect_assigned_idents_in_expr(e, names),
3125
- ArrowBody::Block(s) => Self::collect_assigned_idents_in_stmt(s, names),
3126
- }
3127
- }
3990
+ Expr::ArrowFunction { body, .. } => match body {
3991
+ ArrowBody::Expr(e) => Self::collect_assigned_idents_in_expr(e, names),
3992
+ ArrowBody::Block(s) => Self::collect_assigned_idents_in_stmt(s, names),
3993
+ },
3128
3994
  Expr::Array { elements, .. } => {
3129
3995
  for el in elements {
3130
3996
  match el {
@@ -3152,7 +4018,9 @@ impl Codegen {
3152
4018
  Self::collect_assigned_idents_in_expr(e, names);
3153
4019
  }
3154
4020
  }
3155
- Expr::JsxElement { props, children, .. } => {
4021
+ Expr::JsxElement {
4022
+ props, children, ..
4023
+ } => {
3156
4024
  for p in props {
3157
4025
  match p {
3158
4026
  tishlang_ast::JsxProp::Attr {
@@ -3196,10 +4064,7 @@ impl Codegen {
3196
4064
  Statement::VarDeclDestructure { pattern, .. } => {
3197
4065
  Self::collect_destruct_names(pattern, names);
3198
4066
  }
3199
- Statement::For {
3200
- init: Some(i),
3201
- ..
3202
- } => {
4067
+ Statement::For { init: Some(i), .. } => {
3203
4068
  if let Statement::VarDecl { name, .. } = i.as_ref() {
3204
4069
  names.insert(name.to_string());
3205
4070
  }
@@ -3288,11 +4153,17 @@ impl Codegen {
3288
4153
  }
3289
4154
  match body {
3290
4155
  ArrowBody::Expr(e) => Self::collect_mutated_captures_from_expr(e, block_vars, result),
3291
- ArrowBody::Block(s) => Self::collect_mutated_captures_from_statements(s, block_vars, result),
4156
+ ArrowBody::Block(s) => {
4157
+ Self::collect_mutated_captures_from_statements(s, block_vars, result)
4158
+ }
3292
4159
  }
3293
4160
  }
3294
4161
 
3295
- fn collect_mutated_captures_from_expr(expr: &Expr, block_vars: &HashSet<String>, result: &mut HashSet<String>) {
4162
+ fn collect_mutated_captures_from_expr(
4163
+ expr: &Expr,
4164
+ block_vars: &HashSet<String>,
4165
+ result: &mut HashSet<String>,
4166
+ ) {
3296
4167
  match expr {
3297
4168
  Expr::ArrowFunction { params, body, .. } => {
3298
4169
  Self::collect_mutated_captures_from_arrow(params, body, block_vars, result);
@@ -3323,13 +4194,17 @@ impl Codegen {
3323
4194
  Self::collect_mutated_captures_from_expr(e, block_vars, result);
3324
4195
  }
3325
4196
  }
3326
- Expr::Conditional { cond, then_branch, else_branch, .. } => {
4197
+ Expr::Conditional {
4198
+ cond,
4199
+ then_branch,
4200
+ else_branch,
4201
+ ..
4202
+ } => {
3327
4203
  Self::collect_mutated_captures_from_expr(cond, block_vars, result);
3328
4204
  Self::collect_mutated_captures_from_expr(then_branch, block_vars, result);
3329
4205
  Self::collect_mutated_captures_from_expr(else_branch, block_vars, result);
3330
4206
  }
3331
- Expr::Binary { left, right, .. }
3332
- | Expr::NullishCoalesce { left, right, .. } => {
4207
+ Expr::Binary { left, right, .. } | Expr::NullishCoalesce { left, right, .. } => {
3333
4208
  Self::collect_mutated_captures_from_expr(left, block_vars, result);
3334
4209
  Self::collect_mutated_captures_from_expr(right, block_vars, result);
3335
4210
  }
@@ -3372,14 +4247,25 @@ impl Codegen {
3372
4247
  Self::collect_mutated_captures_from_statements(s, block_vars, result);
3373
4248
  }
3374
4249
  }
3375
- Statement::If { cond, then_branch, else_branch, .. } => {
4250
+ Statement::If {
4251
+ cond,
4252
+ then_branch,
4253
+ else_branch,
4254
+ ..
4255
+ } => {
3376
4256
  Self::collect_mutated_captures_from_expr(cond, block_vars, result);
3377
4257
  Self::collect_mutated_captures_from_statements(then_branch, block_vars, result);
3378
4258
  if let Some(eb) = else_branch {
3379
4259
  Self::collect_mutated_captures_from_statements(eb, block_vars, result);
3380
4260
  }
3381
4261
  }
3382
- Statement::For { init, cond, update, body, .. } => {
4262
+ Statement::For {
4263
+ init,
4264
+ cond,
4265
+ update,
4266
+ body,
4267
+ ..
4268
+ } => {
3383
4269
  if let Some(i) = init {
3384
4270
  Self::collect_mutated_captures_from_statements(i, block_vars, result);
3385
4271
  }
@@ -3399,7 +4285,12 @@ impl Codegen {
3399
4285
  Self::collect_mutated_captures_from_expr(cond, block_vars, result);
3400
4286
  Self::collect_mutated_captures_from_statements(body, block_vars, result);
3401
4287
  }
3402
- Statement::Switch { expr, cases, default_body, .. } => {
4288
+ Statement::Switch {
4289
+ expr,
4290
+ cases,
4291
+ default_body,
4292
+ ..
4293
+ } => {
3403
4294
  Self::collect_mutated_captures_from_expr(expr, block_vars, result);
3404
4295
  for (ce, stmts) in cases {
3405
4296
  if let Some(e) = ce {
@@ -3415,7 +4306,12 @@ impl Codegen {
3415
4306
  }
3416
4307
  }
3417
4308
  }
3418
- Statement::Try { body, catch_body, finally_body, .. } => {
4309
+ Statement::Try {
4310
+ body,
4311
+ catch_body,
4312
+ finally_body,
4313
+ ..
4314
+ } => {
3419
4315
  Self::collect_mutated_captures_from_statements(body, block_vars, result);
3420
4316
  if let Some(c) = catch_body {
3421
4317
  Self::collect_mutated_captures_from_statements(c, block_vars, result);
@@ -3433,7 +4329,9 @@ impl Codegen {
3433
4329
  Statement::Return { value: Some(e), .. } => {
3434
4330
  Self::collect_mutated_captures_from_expr(e, block_vars, result);
3435
4331
  }
3436
- Statement::Throw { value, .. } => Self::collect_mutated_captures_from_expr(value, block_vars, result),
4332
+ Statement::Throw { value, .. } => {
4333
+ Self::collect_mutated_captures_from_expr(value, block_vars, result)
4334
+ }
3437
4335
  _ => {}
3438
4336
  }
3439
4337
  }
@@ -3452,37 +4350,66 @@ impl Codegen {
3452
4350
  /// Collect variable names declared in a statement (VarDecl, Destructure, For init).
3453
4351
  fn collect_local_var_names(stmt: &Statement, names: &mut HashSet<String>) {
3454
4352
  match stmt {
3455
- Statement::VarDecl { name, .. } => { names.insert(name.to_string()); }
4353
+ Statement::VarDecl { name, .. } => {
4354
+ names.insert(name.to_string());
4355
+ }
3456
4356
  Statement::VarDeclDestructure { pattern, .. } => {
3457
4357
  Self::collect_destruct_names(pattern, names);
3458
4358
  }
3459
4359
  Statement::Block { statements, .. } => {
3460
- for s in statements { Self::collect_local_var_names(s, names); }
4360
+ for s in statements {
4361
+ Self::collect_local_var_names(s, names);
4362
+ }
3461
4363
  }
3462
- Statement::If { then_branch, else_branch, .. } => {
4364
+ Statement::If {
4365
+ then_branch,
4366
+ else_branch,
4367
+ ..
4368
+ } => {
3463
4369
  Self::collect_local_var_names(then_branch, names);
3464
- if let Some(eb) = else_branch { Self::collect_local_var_names(eb, names); }
4370
+ if let Some(eb) = else_branch {
4371
+ Self::collect_local_var_names(eb, names);
4372
+ }
3465
4373
  }
3466
4374
  Statement::For { init, body, .. } => {
3467
- if let Some(i) = init { Self::collect_local_var_names(i, names); }
4375
+ if let Some(i) = init {
4376
+ Self::collect_local_var_names(i, names);
4377
+ }
3468
4378
  Self::collect_local_var_names(body, names);
3469
4379
  }
3470
4380
  Statement::ForOf { body, .. } => Self::collect_local_var_names(body, names),
3471
4381
  Statement::While { body, .. } | Statement::DoWhile { body, .. } => {
3472
4382
  Self::collect_local_var_names(body, names);
3473
4383
  }
3474
- Statement::Switch { cases, default_body, .. } => {
4384
+ Statement::Switch {
4385
+ cases,
4386
+ default_body,
4387
+ ..
4388
+ } => {
3475
4389
  for (_, stmts) in cases {
3476
- for s in stmts { Self::collect_local_var_names(s, names); }
4390
+ for s in stmts {
4391
+ Self::collect_local_var_names(s, names);
4392
+ }
3477
4393
  }
3478
4394
  if let Some(stmts) = default_body {
3479
- for s in stmts { Self::collect_local_var_names(s, names); }
4395
+ for s in stmts {
4396
+ Self::collect_local_var_names(s, names);
4397
+ }
3480
4398
  }
3481
4399
  }
3482
- Statement::Try { body, catch_body, finally_body, .. } => {
4400
+ Statement::Try {
4401
+ body,
4402
+ catch_body,
4403
+ finally_body,
4404
+ ..
4405
+ } => {
3483
4406
  Self::collect_local_var_names(body, names);
3484
- if let Some(c) = catch_body { Self::collect_local_var_names(c, names); }
3485
- if let Some(f) = finally_body { Self::collect_local_var_names(f, names); }
4407
+ if let Some(c) = catch_body {
4408
+ Self::collect_local_var_names(c, names);
4409
+ }
4410
+ if let Some(f) = finally_body {
4411
+ Self::collect_local_var_names(f, names);
4412
+ }
3486
4413
  }
3487
4414
  Statement::FunDecl { body, .. } => Self::collect_local_var_names(body, names),
3488
4415
  _ => {}
@@ -3493,16 +4420,24 @@ impl Codegen {
3493
4420
  match pattern {
3494
4421
  DestructPattern::Array(elements) => {
3495
4422
  for el in elements {
3496
- if let Some(DestructElement::Ident(n)) = el { names.insert(n.to_string()); }
3497
- if let Some(DestructElement::Pattern(p)) = el { Self::collect_destruct_names(p, names); }
4423
+ if let Some(DestructElement::Ident(n, _)) = el {
4424
+ names.insert(n.to_string());
4425
+ }
4426
+ if let Some(DestructElement::Pattern(p)) = el {
4427
+ Self::collect_destruct_names(p, names);
4428
+ }
3498
4429
  }
3499
4430
  }
3500
4431
  DestructPattern::Object(props) => {
3501
4432
  for prop in props {
3502
4433
  match &prop.value {
3503
- DestructElement::Ident(n) => { names.insert(n.to_string()); }
4434
+ DestructElement::Ident(n, _) => {
4435
+ names.insert(n.to_string());
4436
+ }
3504
4437
  DestructElement::Pattern(p) => Self::collect_destruct_names(p, names),
3505
- DestructElement::Rest(n) => { names.insert(n.to_string()); }
4438
+ DestructElement::Rest(n, _) => {
4439
+ names.insert(n.to_string());
4440
+ }
3506
4441
  }
3507
4442
  }
3508
4443
  }
@@ -3513,25 +4448,48 @@ impl Codegen {
3513
4448
  match stmt {
3514
4449
  Statement::ExprStmt { expr, .. } => Self::collect_expr_idents(expr, idents),
3515
4450
  Statement::VarDecl { init, .. } => {
3516
- if let Some(e) = init { Self::collect_expr_idents(e, idents); }
4451
+ if let Some(e) = init {
4452
+ Self::collect_expr_idents(e, idents);
4453
+ }
3517
4454
  }
3518
4455
  Statement::VarDeclDestructure { init, .. } => Self::collect_expr_idents(init, idents),
3519
4456
  Statement::Block { statements, .. } => {
3520
- for s in statements { Self::collect_stmt_idents(s, idents); }
4457
+ for s in statements {
4458
+ Self::collect_stmt_idents(s, idents);
4459
+ }
3521
4460
  }
3522
- Statement::If { cond, then_branch, else_branch, .. } => {
4461
+ Statement::If {
4462
+ cond,
4463
+ then_branch,
4464
+ else_branch,
4465
+ ..
4466
+ } => {
3523
4467
  Self::collect_expr_idents(cond, idents);
3524
4468
  Self::collect_stmt_idents(then_branch, idents);
3525
- if let Some(e) = else_branch { Self::collect_stmt_idents(e, idents); }
4469
+ if let Some(e) = else_branch {
4470
+ Self::collect_stmt_idents(e, idents);
4471
+ }
3526
4472
  }
3527
4473
  Statement::While { cond, body, .. } | Statement::DoWhile { body, cond, .. } => {
3528
4474
  Self::collect_expr_idents(cond, idents);
3529
4475
  Self::collect_stmt_idents(body, idents);
3530
4476
  }
3531
- Statement::For { init, cond, update, body, .. } => {
3532
- if let Some(s) = init { Self::collect_stmt_idents(s, idents); }
3533
- if let Some(e) = cond { Self::collect_expr_idents(e, idents); }
3534
- if let Some(e) = update { Self::collect_expr_idents(e, idents); }
4477
+ Statement::For {
4478
+ init,
4479
+ cond,
4480
+ update,
4481
+ body,
4482
+ ..
4483
+ } => {
4484
+ if let Some(s) = init {
4485
+ Self::collect_stmt_idents(s, idents);
4486
+ }
4487
+ if let Some(e) = cond {
4488
+ Self::collect_expr_idents(e, idents);
4489
+ }
4490
+ if let Some(e) = update {
4491
+ Self::collect_expr_idents(e, idents);
4492
+ }
3535
4493
  Self::collect_stmt_idents(body, idents);
3536
4494
  }
3537
4495
  Statement::ForOf { iterable, body, .. } => {
@@ -3539,26 +4497,54 @@ impl Codegen {
3539
4497
  Self::collect_stmt_idents(body, idents);
3540
4498
  }
3541
4499
  Statement::Return { value, .. } => {
3542
- if let Some(e) = value { Self::collect_expr_idents(e, idents); }
4500
+ if let Some(e) = value {
4501
+ Self::collect_expr_idents(e, idents);
4502
+ }
3543
4503
  }
3544
4504
  Statement::Throw { value, .. } => Self::collect_expr_idents(value, idents),
3545
- Statement::Try { body, catch_body, finally_body, .. } => {
4505
+ Statement::Try {
4506
+ body,
4507
+ catch_body,
4508
+ finally_body,
4509
+ ..
4510
+ } => {
3546
4511
  Self::collect_stmt_idents(body, idents);
3547
- if let Some(c) = catch_body { Self::collect_stmt_idents(c, idents); }
3548
- if let Some(f) = finally_body { Self::collect_stmt_idents(f, idents); }
4512
+ if let Some(c) = catch_body {
4513
+ Self::collect_stmt_idents(c, idents);
4514
+ }
4515
+ if let Some(f) = finally_body {
4516
+ Self::collect_stmt_idents(f, idents);
4517
+ }
3549
4518
  }
3550
- Statement::Switch { expr, cases, default_body, .. } => {
4519
+ Statement::Switch {
4520
+ expr,
4521
+ cases,
4522
+ default_body,
4523
+ ..
4524
+ } => {
3551
4525
  Self::collect_expr_idents(expr, idents);
3552
4526
  for (case_expr, stmts) in cases {
3553
- if let Some(e) = case_expr { Self::collect_expr_idents(e, idents); }
3554
- for s in stmts { Self::collect_stmt_idents(s, idents); }
4527
+ if let Some(e) = case_expr {
4528
+ Self::collect_expr_idents(e, idents);
4529
+ }
4530
+ for s in stmts {
4531
+ Self::collect_stmt_idents(s, idents);
4532
+ }
3555
4533
  }
3556
4534
  if let Some(stmts) = default_body {
3557
- for s in stmts { Self::collect_stmt_idents(s, idents); }
4535
+ for s in stmts {
4536
+ Self::collect_stmt_idents(s, idents);
4537
+ }
3558
4538
  }
3559
4539
  }
3560
4540
  Statement::FunDecl { body, .. } => Self::collect_stmt_idents(body, idents),
3561
- Statement::Break { .. } | Statement::Continue { .. } | Statement::Import { .. } | Statement::Export { .. } => {}
4541
+ Statement::Break { .. }
4542
+ | Statement::Continue { .. }
4543
+ | Statement::Import { .. }
4544
+ | Statement::Export { .. }
4545
+ | Statement::TypeAlias { .. }
4546
+ | Statement::DeclareVar { .. }
4547
+ | Statement::DeclareFun { .. } => {}
3562
4548
  }
3563
4549
  }
3564
4550
 
@@ -3571,7 +4557,7 @@ impl Codegen {
3571
4557
  // Build the arrow function as a Value::Function closure
3572
4558
  let mut code = String::new();
3573
4559
  code.push_str("{\n");
3574
-
4560
+
3575
4561
  // Find which identifiers are actually referenced in the body
3576
4562
  let referenced = Self::collect_referenced_idents(body);
3577
4563
  // Exclude the arrow's own parameters - they're not outer captures
@@ -3589,17 +4575,42 @@ impl Codegen {
3589
4575
  }
3590
4576
 
3591
4577
  // Collect outer parameters that need to be captured
3592
- let outer_params: Vec<String> = self.outer_params_stack
4578
+ let outer_params: Vec<String> = self
4579
+ .outer_params_stack
3593
4580
  .iter()
3594
4581
  .flat_map(|p| p.iter().cloned())
3595
4582
  .filter(|name| referenced.contains(name) && !param_names.contains(name))
3596
4583
  .collect();
3597
-
4584
+
3598
4585
  // Collect outer variables (from outer scopes) that need to be captured
3599
- let outer_vars: Vec<String> = self.outer_vars_stack
4586
+ let outer_vars: Vec<String> = self
4587
+ .outer_vars_stack
3600
4588
  .iter()
3601
4589
  .flat_map(|v| v.iter().cloned())
3602
- .filter(|name| referenced.contains(name) && !param_names.contains(name) && !local_var_names.contains(name))
4590
+ .filter(|name| {
4591
+ referenced.contains(name)
4592
+ && !param_names.contains(name)
4593
+ && !local_var_names.contains(name)
4594
+ })
4595
+ .filter(|name| {
4596
+ ![
4597
+ "Boolean",
4598
+ "console",
4599
+ "Math",
4600
+ "JSON",
4601
+ "Date",
4602
+ "Object",
4603
+ "process",
4604
+ "setTimeout",
4605
+ "clearTimeout",
4606
+ "setInterval",
4607
+ "clearInterval",
4608
+ "Promise",
4609
+ "RegExp",
4610
+ "Polars",
4611
+ ]
4612
+ .contains(&name.as_str())
4613
+ })
3603
4614
  .collect();
3604
4615
 
3605
4616
  // Outer vars that are assigned in the body need RefCell; read-only get Value binding
@@ -3608,8 +4619,18 @@ impl Codegen {
3608
4619
  ArrowBody::Expr(e) => Self::collect_assigned_idents_in_expr(e, &mut assigned_in_body),
3609
4620
  ArrowBody::Block(s) => Self::collect_assigned_idents_in_stmt(s, &mut assigned_in_body),
3610
4621
  }
3611
- let mutable_outer_vars: Vec<String> = outer_vars.iter().filter(|v| assigned_in_body.contains(*v)).cloned().collect();
3612
- let read_only_outer_vars: Vec<String> = outer_vars.iter().filter(|v| !assigned_in_body.contains(*v)).cloned().collect();
4622
+ // Live cell capture: assigned here, or already `Rc<RefCell<Value>>` in a parent scope
4623
+ // (cleanups may only read `timer2` but must see updates from nested callbacks).
4624
+ let cell_capture_outer_vars: Vec<String> = outer_vars
4625
+ .iter()
4626
+ .filter(|v| assigned_in_body.contains(*v) || self.rc_cell_storage_contains(*v))
4627
+ .cloned()
4628
+ .collect();
4629
+ let read_only_outer_vars: Vec<String> = outer_vars
4630
+ .iter()
4631
+ .filter(|v| !assigned_in_body.contains(*v) && !self.rc_cell_storage_contains(*v))
4632
+ .cloned()
4633
+ .collect();
3613
4634
 
3614
4635
  // Wrap outer captures in Rc<RefCell<>> and use _ref suffix.
3615
4636
  // Clone existing Rc only when VarDecl actually emitted `Rc<RefCell<...>>` (see rc_cell_storage_*).
@@ -3617,62 +4638,110 @@ impl Codegen {
3617
4638
  let param_escaped = Self::escape_ident(outer_param);
3618
4639
  let ref_name = format!("{}_ref", param_escaped);
3619
4640
  if self.rc_cell_storage_contains(outer_param) {
3620
- code.push_str(&format!(" let {} = {}.clone();\n", ref_name, param_escaped));
4641
+ code.push_str(&format!(
4642
+ " let {} = {}.clone();\n",
4643
+ ref_name, param_escaped
4644
+ ));
3621
4645
  } else {
3622
- code.push_str(&format!(" let {} = std::rc::Rc::new(RefCell::new({}.clone()));\n", ref_name, param_escaped));
4646
+ code.push_str(&format!(
4647
+ " let {} = VmRef::new({}.clone());\n",
4648
+ ref_name, param_escaped
4649
+ ));
3623
4650
  }
3624
4651
  }
3625
4652
  for outer_var in &outer_vars {
3626
4653
  let var_escaped = Self::escape_ident(outer_var);
3627
4654
  let ref_name = format!("{}_ref", var_escaped);
3628
4655
  if self.rc_cell_storage_contains(outer_var) {
3629
- code.push_str(&format!(" let {} = {}.clone();\n", ref_name, var_escaped));
4656
+ code.push_str(&format!(
4657
+ " let {} = {}.clone();\n",
4658
+ ref_name, var_escaped
4659
+ ));
3630
4660
  } else {
3631
- code.push_str(&format!(" let {} = std::rc::Rc::new(RefCell::new({}.clone()));\n", ref_name, var_escaped));
4661
+ code.push_str(&format!(
4662
+ " let {} = VmRef::new({}.clone());\n",
4663
+ ref_name, var_escaped
4664
+ ));
3632
4665
  }
3633
4666
  }
3634
4667
  // Only clone builtins that are actually referenced (clone so outer scope can still use, e.g. process for PORT)
3635
- for builtin in &["console", "Math", "JSON", "Date", "Uint8Array", "AudioContext", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"] {
4668
+ for builtin in &[
4669
+ "console",
4670
+ "Math",
4671
+ "JSON",
4672
+ "Date",
4673
+ "Object",
4674
+ "Uint8Array",
4675
+ "AudioContext",
4676
+ "process",
4677
+ "setTimeout",
4678
+ "clearTimeout",
4679
+ "setInterval",
4680
+ "clearInterval",
4681
+ "Promise",
4682
+ "RegExp",
4683
+ "Polars",
4684
+ ] {
3636
4685
  if referenced.contains(*builtin) {
3637
4686
  code.push_str(&format!(" let {} = {}.clone();\n", builtin, builtin));
3638
4687
  }
3639
4688
  }
3640
4689
 
3641
4690
  // Clone only function cells that are actually referenced in this arrow
3642
- let referenced_funcs: Vec<String> = self.function_scope_stack
4691
+ let referenced_funcs: Vec<String> = self
4692
+ .function_scope_stack
3643
4693
  .last()
3644
- .map(|scope| scope.iter()
3645
- .filter(|f| referenced.contains(f.as_str()) && !param_names.contains(*f))
3646
- .cloned()
3647
- .collect())
4694
+ .map(|scope| {
4695
+ scope
4696
+ .iter()
4697
+ .filter(|f| referenced.contains(f.as_str()) && !param_names.contains(*f))
4698
+ .cloned()
4699
+ .collect()
4700
+ })
3648
4701
  .unwrap_or_default();
3649
4702
  for func_name in &referenced_funcs {
3650
4703
  let escaped = Self::escape_ident(func_name);
3651
- code.push_str(&format!(" let {}_ref = {}_cell.clone();\n", escaped, escaped));
4704
+ code.push_str(&format!(
4705
+ " let {}_ref = {}_cell.clone();\n",
4706
+ escaped, escaped
4707
+ ));
3652
4708
  }
3653
4709
 
3654
- code.push_str(" Value::Function(Rc::new(move |args: &[Value]| {\n");
4710
+ code.push_str(" Value::native(move |args: &[Value]| {\n");
4711
+ self.value_fn_depth += 1;
3655
4712
 
3656
4713
  // Make captured outer params available as plain Values (from _ref RefCells)
3657
4714
  for outer_param in &outer_params {
3658
4715
  let param_escaped = Self::escape_ident(outer_param);
3659
- code.push_str(&format!(" let {} = (*{}_ref.borrow()).clone();\n", param_escaped, param_escaped));
4716
+ code.push_str(&format!(
4717
+ " let {} = (*{}_ref.borrow()).clone();\n",
4718
+ param_escaped, param_escaped
4719
+ ));
3660
4720
  }
3661
- // Mutable outer vars: capture RefCell so assignments use borrow_mut
3662
- for outer_var in &mutable_outer_vars {
4721
+ // Outer vars that share a RefCell with the parent: capture the cell (read + write)
4722
+ for outer_var in &cell_capture_outer_vars {
3663
4723
  let var_escaped = Self::escape_ident(outer_var);
3664
- code.push_str(&format!(" let {} = {}_ref.clone();\n", var_escaped, var_escaped));
4724
+ code.push_str(&format!(
4725
+ " let {} = {}_ref.clone();\n",
4726
+ var_escaped, var_escaped
4727
+ ));
3665
4728
  }
3666
- // Read-only outer vars: Value binding from borrow
4729
+ // Read-only outer vars: snapshot Value at closure creation
3667
4730
  for outer_var in &read_only_outer_vars {
3668
4731
  let var_escaped = Self::escape_ident(outer_var);
3669
- code.push_str(&format!(" let {} = (*{}_ref.borrow()).clone();\n", var_escaped, var_escaped));
4732
+ code.push_str(&format!(
4733
+ " let {} = (*{}_ref.borrow()).clone();\n",
4734
+ var_escaped, var_escaped
4735
+ ));
3670
4736
  }
3671
4737
 
3672
4738
  // Make captured functions available
3673
4739
  for func_name in &referenced_funcs {
3674
4740
  let escaped = Self::escape_ident(func_name);
3675
- code.push_str(&format!(" let {} = (*{}_ref.borrow()).clone();\n", escaped, escaped));
4741
+ code.push_str(&format!(
4742
+ " let {} = (*{}_ref.borrow()).clone();\n",
4743
+ escaped, escaped
4744
+ ));
3676
4745
  }
3677
4746
 
3678
4747
  // Extract parameters from args
@@ -3712,44 +4781,55 @@ impl Codegen {
3712
4781
  // Push empty scope for variables declared inside this arrow function
3713
4782
  self.outer_vars_stack.push(Vec::new());
3714
4783
 
3715
- // Mutable outer vars need to be in refcell_wrapped_vars so Assign/CompoundAssign emit borrow_mut
4784
+ // Cell-backed outer vars need refcell_wrapped_vars for Assign and for reads in emit_expr
3716
4785
  let saved_refcell_vars = self.refcell_wrapped_vars.clone();
3717
- for v in &mutable_outer_vars {
4786
+ for v in &cell_capture_outer_vars {
3718
4787
  self.refcell_wrapped_vars.insert(v.clone());
3719
4788
  }
3720
4789
 
3721
- // Emit body based on type
3722
- match body {
4790
+ self.type_context.push_fun_param_scope(params, None);
4791
+
4792
+ let arrow_body_res: Result<(), CompileError> = match body {
3723
4793
  tishlang_ast::ArrowBody::Expr(expr) => {
3724
4794
  let expr_code = self.emit_expr(expr)?;
3725
4795
  code.push_str(&format!(" {}\n", expr_code));
4796
+ Ok(())
3726
4797
  }
3727
4798
  tishlang_ast::ArrowBody::Block(block_stmt) => {
3728
4799
  // For block bodies, emit the block statement
3729
4800
  self.function_scope_stack.push(Vec::new());
3730
-
4801
+
3731
4802
  // Save current output, emit to temp, then restore
3732
4803
  let saved_output = std::mem::take(&mut self.output);
3733
4804
  let saved_indent = self.indent;
3734
4805
  self.indent = 2; // Base indent inside the closure
3735
-
4806
+
3736
4807
  self.emit_statement(block_stmt)?;
3737
-
4808
+
3738
4809
  let body_code = std::mem::replace(&mut self.output, saved_output);
3739
4810
  self.indent = saved_indent;
3740
4811
  self.function_scope_stack.pop();
3741
-
4812
+
3742
4813
  code.push_str(&body_code);
3743
4814
  code.push_str(" Value::Null\n");
4815
+ Ok(())
3744
4816
  }
4817
+ };
4818
+
4819
+ self.type_context.pop_scope();
4820
+ if let Err(e) = arrow_body_res {
4821
+ self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
4822
+ return Err(e);
3745
4823
  }
3746
4824
 
4825
+ self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
4826
+
3747
4827
  // Restore state
3748
4828
  self.refcell_wrapped_vars = saved_refcell_vars;
3749
4829
  self.outer_params_stack.pop();
3750
4830
  self.outer_vars_stack.pop();
3751
4831
 
3752
- code.push_str(" }))\n");
4832
+ code.push_str(" })\n");
3753
4833
  code.push('}');
3754
4834
 
3755
4835
  Ok(code)
@@ -3758,7 +4838,11 @@ impl Codegen {
3758
4838
  /// Emit an expression as a native Rust type (not wrapped in Value).
3759
4839
  /// Falls back to emit_expr + conversion if the expression cannot be directly
3760
4840
  /// emitted as the target type.
3761
- fn emit_native_expr(&mut self, expr: &Expr, target_type: &RustType) -> Result<String, CompileError> {
4841
+ fn emit_native_expr(
4842
+ &mut self,
4843
+ expr: &Expr,
4844
+ target_type: &RustType,
4845
+ ) -> Result<String, CompileError> {
3762
4846
  // Try to emit literals directly as native types
3763
4847
  if let Expr::Literal { value, .. } = expr {
3764
4848
  match (target_type, value) {
@@ -3777,7 +4861,7 @@ impl Codegen {
3777
4861
  _ => {}
3778
4862
  }
3779
4863
  }
3780
-
4864
+
3781
4865
  // Try to emit array literals directly as Vec<T>
3782
4866
  if let (RustType::Vec(inner_type), Expr::Array { elements, .. }) = (target_type, expr) {
3783
4867
  let mut items = Vec::new();
@@ -3796,7 +4880,58 @@ impl Codegen {
3796
4880
  }
3797
4881
  return Ok(format!("vec![{}]", items.join(", ")));
3798
4882
  }
3799
-
4883
+
4884
+ // Try to emit object literals directly as a Rust struct literal
4885
+ // when the target is a `RustType::Named` (a user `type Foo = {...}`
4886
+ // alias). Each property in source order is matched to a struct
4887
+ // field; missing fields fall back to `default_value()` so the
4888
+ // emit succeeds even on partial literals (rare, but harmless).
4889
+ if let (RustType::Named { name, fields }, Expr::Object { props, .. }) =
4890
+ (target_type, expr)
4891
+ {
4892
+ use std::collections::HashMap;
4893
+ let field_types: HashMap<&str, &RustType> = fields
4894
+ .iter()
4895
+ .map(|(k, t)| (k.as_ref(), t))
4896
+ .collect();
4897
+ let mut field_inits: HashMap<String, String> = HashMap::new();
4898
+ let mut bail = false;
4899
+ for prop in props {
4900
+ match prop {
4901
+ ObjectProp::KeyValue(key, value) => {
4902
+ if let Some(field_ty) = field_types.get(key.as_ref()) {
4903
+ let v = self.emit_native_expr(value, field_ty)?;
4904
+ field_inits.insert(crate::types::field_ident(key.as_ref()), v);
4905
+ }
4906
+ }
4907
+ // Spread can't be statically matched to struct fields:
4908
+ // fall back to the dynamic Value path.
4909
+ ObjectProp::Spread(_) => {
4910
+ bail = true;
4911
+ break;
4912
+ }
4913
+ }
4914
+ }
4915
+ if !bail {
4916
+ let assigns = fields
4917
+ .iter()
4918
+ .map(|(k, t)| {
4919
+ let fid = crate::types::field_ident(k);
4920
+ match field_inits.remove(&fid) {
4921
+ Some(v) => format!("{}: {}", fid, v),
4922
+ None => format!("{}: {}", fid, t.default_value()),
4923
+ }
4924
+ })
4925
+ .collect::<Vec<_>>()
4926
+ .join(", ");
4927
+ return Ok(format!(
4928
+ "{} {{ {} }}",
4929
+ crate::types::named_struct_ident(name),
4930
+ assigns
4931
+ ));
4932
+ }
4933
+ }
4934
+
3800
4935
  // Check if the identifier is already of the target type
3801
4936
  if let Expr::Ident { name, .. } = expr {
3802
4937
  let var_type = self.type_context.get_type(name.as_ref());
@@ -3808,7 +4943,7 @@ impl Codegen {
3808
4943
  return Ok(esc);
3809
4944
  }
3810
4945
  }
3811
-
4946
+
3812
4947
  // Fall back to emit_expr + conversion
3813
4948
  let value_expr = self.emit_expr(expr)?;
3814
4949
  Ok(target_type.from_value_expr(&value_expr))
@@ -3829,7 +4964,9 @@ impl Codegen {
3829
4964
  // ── literals ─────────────────────────────────────────────────────────
3830
4965
  Expr::Literal { value, .. } => match value {
3831
4966
  Literal::Number(n) => Ok((format!("{}_f64", n), RustType::F64)),
3832
- Literal::String(s) => Ok((format!("{:?}.to_string()", s.as_ref()), RustType::String)),
4967
+ Literal::String(s) => {
4968
+ Ok((format!("{:?}.to_string()", s.as_ref()), RustType::String))
4969
+ }
3833
4970
  Literal::Bool(b) => Ok((format!("{}", b), RustType::Bool)),
3834
4971
  Literal::Null => Ok(("Value::Null".to_string(), RustType::Value)),
3835
4972
  },
@@ -3855,7 +4992,13 @@ impl Codegen {
3855
4992
  }
3856
4993
 
3857
4994
  // ── binary expressions ───────────────────────────────────────────────
3858
- Expr::Binary { left, op, right, span, .. } => {
4995
+ Expr::Binary {
4996
+ left,
4997
+ op,
4998
+ right,
4999
+ span,
5000
+ ..
5001
+ } => {
3859
5002
  let (l, lt) = self.emit_typed_expr(left)?;
3860
5003
  let (r, rt) = self.emit_typed_expr(right)?;
3861
5004
 
@@ -3882,14 +5025,27 @@ impl Codegen {
3882
5025
  }
3883
5026
 
3884
5027
  // Fall back: convert both sides to Value and use the runtime.
3885
- let lv = if lt.is_native() { lt.to_value_expr(&l) } else { l };
3886
- let rv = if rt.is_native() { rt.to_value_expr(&r) } else { r };
5028
+ let lv = if lt.is_native() {
5029
+ lt.to_value_expr(&l)
5030
+ } else {
5031
+ l
5032
+ };
5033
+ let rv = if rt.is_native() {
5034
+ rt.to_value_expr(&r)
5035
+ } else {
5036
+ r
5037
+ };
3887
5038
  let result = self.emit_binop(&lv, *op, &rv, *span)?;
3888
5039
  Ok((result, RustType::Value))
3889
5040
  }
3890
5041
 
3891
5042
  // ── array indexing ───────────────────────────────────────────────────
3892
- Expr::Index { object, index, optional, .. } => {
5043
+ Expr::Index {
5044
+ object,
5045
+ index,
5046
+ optional,
5047
+ ..
5048
+ } => {
3893
5049
  // Native fast path: `vec[i]` where vec is Vec<T> and i is numeric.
3894
5050
  if !optional {
3895
5051
  if let Expr::Ident { name, .. } = object.as_ref() {
@@ -3958,19 +5114,28 @@ impl Codegen {
3958
5114
  }
3959
5115
  }
3960
5116
 
3961
- fn emit_binop(
3962
- &self,
3963
- l: &str,
3964
- op: BinOp,
3965
- r: &str,
3966
- span: Span,
3967
- ) -> Result<String, CompileError> {
5117
+ fn emit_binop(&self, l: &str, op: BinOp, r: &str, span: Span) -> Result<String, CompileError> {
3968
5118
  Ok(match op {
3969
- BinOp::Add => format!("tishlang_runtime::ops::add(&{}, &{}).unwrap_or(Value::Null)", l, r),
3970
- BinOp::Sub => format!("tishlang_runtime::ops::sub(&{}, &{}).unwrap_or(Value::Null)", l, r),
3971
- BinOp::Mul => format!("tishlang_runtime::ops::mul(&{}, &{}).unwrap_or(Value::Null)", l, r),
3972
- BinOp::Div => format!("tishlang_runtime::ops::div(&{}, &{}).unwrap_or(Value::Null)", l, r),
3973
- BinOp::Mod => format!("tishlang_runtime::ops::modulo(&{}, &{}).unwrap_or(Value::Null)", l, r),
5119
+ BinOp::Add => format!(
5120
+ "tishlang_runtime::ops::add(&{}, &{}).unwrap_or(Value::Null)",
5121
+ l, r
5122
+ ),
5123
+ BinOp::Sub => format!(
5124
+ "tishlang_runtime::ops::sub(&{}, &{}).unwrap_or(Value::Null)",
5125
+ l, r
5126
+ ),
5127
+ BinOp::Mul => format!(
5128
+ "tishlang_runtime::ops::mul(&{}, &{}).unwrap_or(Value::Null)",
5129
+ l, r
5130
+ ),
5131
+ BinOp::Div => format!(
5132
+ "tishlang_runtime::ops::div(&{}, &{}).unwrap_or(Value::Null)",
5133
+ l, r
5134
+ ),
5135
+ BinOp::Mod => format!(
5136
+ "tishlang_runtime::ops::modulo(&{}, &{}).unwrap_or(Value::Null)",
5137
+ l, r
5138
+ ),
3974
5139
  BinOp::Pow => format!(
3975
5140
  "Value::Number({{ let Value::Number(a) = &({}) else {{ panic!() }}; \
3976
5141
  let Value::Number(b) = &({}) else {{ panic!() }}; a.powf(*b) }})",
@@ -3991,7 +5156,10 @@ impl Codegen {
3991
5156
  BinOp::Shr => Self::emit_bitwise_binop(l, r, ">>"),
3992
5157
  BinOp::In => format!("tish_in_operator(&{}, &{})", l, r),
3993
5158
  BinOp::Eq | BinOp::Ne => {
3994
- return Err(CompileError::new("Loose equality not supported", Some(span)))
5159
+ return Err(CompileError::new(
5160
+ "Loose equality not supported",
5161
+ Some(span),
5162
+ ))
3995
5163
  }
3996
5164
  })
3997
5165
  }