@tishlang/tish 1.7.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 (95) hide show
  1. package/Cargo.toml +1 -0
  2. package/README.md +2 -0
  3. package/bin/tish +0 -0
  4. package/crates/js_to_tish/src/transform/expr.rs +28 -8
  5. package/crates/js_to_tish/src/transform/stmt.rs +49 -22
  6. package/crates/tish/Cargo.toml +15 -5
  7. package/crates/tish/src/cargo_native_registry.rs +29 -0
  8. package/crates/tish/src/cli_help.rs +16 -10
  9. package/crates/tish/src/main.rs +87 -32
  10. package/crates/tish/src/repl_completion.rs +3 -3
  11. package/crates/tish/tests/cargo_example_compile.rs +1 -1
  12. package/crates/tish/tests/integration_test.rs +19 -7
  13. package/crates/tish/tests/shortcircuit.rs +1 -1
  14. package/crates/tish_ast/src/ast.rs +80 -9
  15. package/crates/tish_build_utils/Cargo.toml +4 -0
  16. package/crates/tish_build_utils/src/lib.rs +105 -2
  17. package/crates/tish_builtins/Cargo.toml +5 -1
  18. package/crates/tish_builtins/src/array.rs +13 -12
  19. package/crates/tish_builtins/src/construct.rs +34 -33
  20. package/crates/tish_builtins/src/globals.rs +12 -11
  21. package/crates/tish_builtins/src/helpers.rs +2 -1
  22. package/crates/tish_builtins/src/object.rs +3 -2
  23. package/crates/tish_builtins/src/string.rs +73 -3
  24. package/crates/tish_bytecode/src/compiler.rs +12 -14
  25. package/crates/tish_bytecode/src/opcode.rs +12 -3
  26. package/crates/tish_compile/Cargo.toml +1 -0
  27. package/crates/tish_compile/src/codegen.rs +745 -199
  28. package/crates/tish_compile/src/infer.rs +6 -0
  29. package/crates/tish_compile/src/lib.rs +4 -3
  30. package/crates/tish_compile/src/resolve.rs +180 -82
  31. package/crates/tish_compile/src/types.rs +175 -11
  32. package/crates/tish_compile_js/Cargo.toml +1 -0
  33. package/crates/tish_compile_js/src/codegen.rs +152 -29
  34. package/crates/tish_compile_js/src/lib.rs +3 -1
  35. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +31 -12
  36. package/crates/tish_core/Cargo.toml +8 -0
  37. package/crates/tish_core/src/json.rs +102 -53
  38. package/crates/tish_core/src/lib.rs +3 -1
  39. package/crates/tish_core/src/macros.rs +5 -5
  40. package/crates/tish_core/src/value.rs +53 -15
  41. package/crates/tish_core/src/vmref.rs +178 -0
  42. package/crates/tish_eval/Cargo.toml +17 -2
  43. package/crates/tish_eval/src/eval.rs +90 -28
  44. package/crates/tish_eval/src/http.rs +61 -0
  45. package/crates/tish_eval/src/lib.rs +3 -3
  46. package/crates/tish_eval/src/natives.rs +41 -0
  47. package/crates/tish_eval/src/value.rs +7 -3
  48. package/crates/tish_eval/src/value_convert.rs +13 -5
  49. package/crates/tish_fmt/src/lib.rs +120 -30
  50. package/crates/tish_lexer/src/lib.rs +20 -5
  51. package/crates/tish_lexer/src/token.rs +4 -0
  52. package/crates/tish_llvm/src/lib.rs +3 -1
  53. package/crates/tish_lsp/Cargo.toml +4 -1
  54. package/crates/tish_lsp/README.md +1 -1
  55. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  56. package/crates/tish_lsp/src/import_goto.rs +549 -0
  57. package/crates/tish_lsp/src/main.rs +502 -102
  58. package/crates/tish_native/src/build.rs +3 -2
  59. package/crates/tish_native/src/lib.rs +6 -2
  60. package/crates/tish_opt/src/lib.rs +17 -2
  61. package/crates/tish_parser/src/lib.rs +10 -3
  62. package/crates/tish_parser/src/parser.rs +346 -56
  63. package/crates/tish_resolve/Cargo.toml +13 -0
  64. package/crates/tish_resolve/src/lib.rs +3436 -0
  65. package/crates/tish_resolve/src/pos.rs +133 -0
  66. package/crates/tish_runtime/Cargo.toml +68 -3
  67. package/crates/tish_runtime/src/http.rs +1123 -141
  68. package/crates/tish_runtime/src/http_fetch.rs +15 -14
  69. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  70. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  71. package/crates/tish_runtime/src/lib.rs +159 -29
  72. package/crates/tish_runtime/src/promise.rs +199 -36
  73. package/crates/tish_runtime/src/promise_io.rs +2 -1
  74. package/crates/tish_runtime/src/timers.rs +37 -1
  75. package/crates/tish_runtime/src/ws.rs +26 -28
  76. package/crates/tish_ui/src/jsx.rs +279 -8
  77. package/crates/tish_ui/src/lib.rs +5 -2
  78. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  79. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  80. package/crates/tish_vm/Cargo.toml +15 -5
  81. package/crates/tish_vm/src/vm.rs +506 -259
  82. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +3 -1
  83. package/crates/tish_wasm/src/lib.rs +17 -14
  84. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  85. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  86. package/crates/tishlang_cargo_bindgen/Cargo.toml +1 -0
  87. package/crates/tishlang_cargo_bindgen/src/discover.rs +68 -0
  88. package/crates/tishlang_cargo_bindgen/src/lib.rs +5 -4
  89. package/justfile +8 -0
  90. package/package.json +1 -1
  91. package/platform/darwin-arm64/tish +0 -0
  92. package/platform/darwin-x64/tish +0 -0
  93. package/platform/linux-arm64/tish +0 -0
  94. package/platform/linux-x64/tish +0 -0
  95. package/platform/win32-x64/tish.exe +0 -0
@@ -7,7 +7,8 @@ use std::collections::{HashMap, HashSet};
7
7
  use std::path::Path;
8
8
  use tishlang_ast::{
9
9
  ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern, Expr,
10
- FunParam, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement, UnaryOp,
10
+ FunParam, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement,
11
+ TypeAnnotation, UnaryOp,
11
12
  };
12
13
 
13
14
  /// Tracks variable usage for move/clone optimization.
@@ -119,7 +120,11 @@ impl UsageAnalyzer {
119
120
  self.analyze_statement(body);
120
121
  }
121
122
  Statement::Break { .. } | Statement::Continue { .. } => {}
122
- Statement::Import { .. } | Statement::Export { .. } => {
123
+ Statement::Import { .. }
124
+ | Statement::Export { .. }
125
+ | Statement::TypeAlias { .. }
126
+ | Statement::DeclareVar { .. }
127
+ | Statement::DeclareFun { .. } => {
123
128
  // Import/Export should be resolved by merge_modules before compilation
124
129
  }
125
130
  }
@@ -551,23 +556,27 @@ pub fn compile_project_full(
551
556
  span: None,
552
557
  })?;
553
558
  let native_modules =
554
- resolve::resolve_native_modules(&merged, root).map_err(|e| CompileError {
559
+ resolve::resolve_native_modules(&merged.program, root).map_err(|e| CompileError {
555
560
  message: e,
556
561
  span: None,
557
562
  })?;
558
- let native_build = resolve::compute_native_build_artifacts(&merged, root, &native_modules)
563
+ let native_build = resolve::compute_native_build_artifacts(
564
+ &merged.program,
565
+ root,
566
+ &native_modules,
567
+ )
559
568
  .map_err(|e| CompileError {
560
569
  message: e,
561
570
  span: None,
562
571
  })?;
563
572
  let mut all_features: Vec<String> = features.to_vec();
564
- for f in resolve::extract_native_import_features(&merged) {
573
+ for f in resolve::extract_native_import_features(&merged.program) {
565
574
  if !all_features.contains(&f) {
566
575
  all_features.push(f);
567
576
  }
568
577
  }
569
578
  let rust = compile_with_native_modules(
570
- &merged,
579
+ &merged.program,
571
580
  project_root,
572
581
  &all_features,
573
582
  &native_modules,
@@ -659,8 +668,20 @@ struct Codegen {
659
668
  usage_analyzer: Option<UsageAnalyzer>,
660
669
  /// Type context for tracking variable types (for static typing)
661
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>,
662
677
  /// Program uses JSX; emit `tishlang_ui` imports and `h` / `Fragment` globals.
663
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,
664
685
  }
665
686
 
666
687
  impl Codegen {
@@ -687,7 +708,191 @@ impl Codegen {
687
708
  rc_cell_storage_scopes: vec![std::collections::HashSet::new()],
688
709
  usage_analyzer: None,
689
710
  type_context: TypeContext::new(),
711
+ type_aliases: std::collections::HashMap::new(),
690
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");
691
896
  }
692
897
  }
693
898
 
@@ -733,39 +938,47 @@ impl Codegen {
733
938
  fn builtin_native_module_rust_init(&self, spec: &str, export_name: &str) -> Option<String> {
734
939
  let init = match spec {
735
940
  "tish:fs" if self.has_feature("fs") => match export_name {
736
- "readFile" => Some("Value::Function(Rc::new(|args: &[Value]| tish_read_file(args)))"),
737
- "writeFile" => Some("Value::Function(Rc::new(|args: &[Value]| tish_write_file(args)))"),
738
- "fileExists" => Some("Value::Function(Rc::new(|args: &[Value]| tish_file_exists(args)))"),
739
- "isDir" => Some("Value::Function(Rc::new(|args: &[Value]| tish_is_dir(args)))"),
740
- "readDir" => Some("Value::Function(Rc::new(|args: &[Value]| tish_read_dir(args)))"),
741
- "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))"),
742
947
  _ => None,
743
948
  },
744
949
  "tish:http" if self.has_feature("http") => match export_name {
745
- "fetch" => Some("Value::Function(Rc::new(|args: &[Value]| tish_fetch_promise(args.to_vec())))"),
746
- "fetchAll" => Some("Value::Function(Rc::new(|args: &[Value]| tish_fetch_all_promise(args.to_vec())))"),
747
- "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 } })"),
748
958
  "Promise" => Some("tish_promise_object()"),
749
- "setTimeout" => Some("Value::Function(Rc::new(|args: &[Value]| tish_timer_set_timeout(args)))"),
750
- "setInterval" => Some("Value::Function(Rc::new(|_args: &[Value]| panic!(\"setInterval not yet supported in native\")))"),
751
- "clearTimeout" => Some("Value::Function(Rc::new(|args: &[Value]| tish_timer_clear_timeout(args)))"),
752
- "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))"),
753
966
  _ => None,
754
967
  },
755
968
  "tish:process" if self.has_feature("process") => match export_name {
756
- "exit" => Some("Value::Function(Rc::new(|args: &[Value]| tish_process_exit(args)))"),
757
- "cwd" => Some("Value::Function(Rc::new(|args: &[Value]| tish_process_cwd(args)))"),
758
- "exec" => Some("Value::Function(Rc::new(|args: &[Value]| tish_process_exec(args)))"),
759
- "argv" => Some("Value::Array(Rc::new(RefCell::new(std::env::args().map(|s| Value::String(s.into())).collect())))"),
760
- "env" => Some("Value::Object(Rc::new(RefCell::new(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect())))"),
761
- "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)) }"),
762
975
  _ => None,
763
976
  },
764
977
  "tish:ws" if self.has_feature("ws") => match export_name {
765
- "WebSocket" => Some("Value::Function(Rc::new(|args: &[Value]| tish_ws_client(args)))"),
766
- "Server" => Some("Value::Function(Rc::new(|args: &[Value]| tish_ws_server_construct(args)))"),
767
- "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()))))"),
768
- "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))"),
769
982
  _ => None,
770
983
  },
771
984
  _ => return None,
@@ -775,7 +988,7 @@ impl Codegen {
775
988
 
776
989
  fn has_feature(&self, name: &str) -> bool {
777
990
  if self.features.contains("full") {
778
- matches!(name, "http" | "fs" | "process" | "regex" | "ws")
991
+ matches!(name, "http" | "timers" | "fs" | "process" | "regex" | "ws")
779
992
  } else {
780
993
  self.features.contains(name)
781
994
  }
@@ -1008,22 +1221,26 @@ impl Codegen {
1008
1221
  fn emit_program(&mut self, program: &Program) -> Result<(), CompileError> {
1009
1222
  self.is_async = program_uses_async(program);
1010
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);
1011
1225
  self.write("#![allow(unused, non_snake_case)]\n\n");
1012
1226
  self.write("use std::cell::RefCell;\n");
1013
1227
  self.write("use std::rc::Rc;\n");
1014
1228
  self.write("use std::sync::Arc;\n");
1015
- 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");
1016
1230
  if self.program_has_jsx {
1017
1231
  self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
1018
1232
  }
1019
1233
  if self.has_feature("process") {
1020
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");
1021
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
+ }
1022
1239
  if self.has_feature("http") {
1023
1240
  if self.is_async {
1024
- 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");
1025
1242
  } else {
1026
- 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");
1027
1244
  }
1028
1245
  }
1029
1246
  if self.has_feature("fs") {
@@ -1037,6 +1254,19 @@ impl Codegen {
1037
1254
  }
1038
1255
  self.write("\n");
1039
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
+
1040
1270
  if self.is_async {
1041
1271
  self.writeln("#[tokio::main]");
1042
1272
  self.writeln("async fn main() {");
@@ -1065,126 +1295,132 @@ impl Codegen {
1065
1295
  self.indent += 1;
1066
1296
 
1067
1297
  // Initialize builtins
1068
- 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([");
1069
1299
  self.indent += 1;
1070
- self.writeln("(Arc::from(\"debug\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_debug(args); Value::Null }))),");
1071
- self.writeln("(Arc::from(\"info\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_info(args); Value::Null }))),");
1072
- self.writeln("(Arc::from(\"log\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_log(args); Value::Null }))),");
1073
- self.writeln("(Arc::from(\"warn\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_warn(args); Value::Null }))),");
1074
- 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 })),");
1075
1305
  self.indent -= 1;
1076
- self.writeln("]))));");
1306
+ self.writeln("])));");
1077
1307
  self.writeln(
1078
- "let Boolean = Value::Function(Rc::new(|args: &[Value]| tish_boolean(args)));",
1308
+ "let Boolean = Value::native(|args: &[Value]| tish_boolean(args));",
1079
1309
  );
1080
1310
  self.writeln(
1081
- "let parseInt = Value::Function(Rc::new(|args: &[Value]| tish_parse_int(args)));",
1311
+ "let parseInt = Value::native(|args: &[Value]| tish_parse_int(args));",
1082
1312
  );
1083
1313
  self.writeln(
1084
- "let parseFloat = Value::Function(Rc::new(|args: &[Value]| tish_parse_float(args)));",
1314
+ "let parseFloat = Value::native(|args: &[Value]| tish_parse_float(args));",
1085
1315
  );
1086
1316
  self.writeln(
1087
- "let decodeURI = Value::Function(Rc::new(|args: &[Value]| tish_decode_uri(args)));",
1317
+ "let decodeURI = Value::native(|args: &[Value]| tish_decode_uri(args));",
1088
1318
  );
1089
1319
  self.writeln(
1090
- "let encodeURI = Value::Function(Rc::new(|args: &[Value]| tish_encode_uri(args)));",
1320
+ "let encodeURI = Value::native(|args: &[Value]| tish_encode_uri(args));",
1091
1321
  );
1092
1322
  self.writeln(
1093
- "let isFinite = Value::Function(Rc::new(|args: &[Value]| tish_is_finite(args)));",
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 });"#,
1094
1324
  );
1095
- self.writeln("let isNaN = Value::Function(Rc::new(|args: &[Value]| tish_is_nan(args)));");
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));");
1096
1332
  self.writeln("let Infinity = Value::Number(f64::INFINITY);");
1097
1333
  self.writeln("let NaN = Value::Number(f64::NAN);");
1098
- self.writeln("let Math = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
1334
+ self.writeln("let Math = Value::Object(VmRef::new(ObjectMap::from([");
1099
1335
  self.indent += 1;
1100
1336
  self.writeln(
1101
- "(Arc::from(\"abs\"), Value::Function(Rc::new(|args: &[Value]| tish_math_abs(args)))),",
1337
+ "(Arc::from(\"abs\"), Value::native(|args: &[Value]| tish_math_abs(args))),",
1102
1338
  );
1103
- self.writeln("(Arc::from(\"sqrt\"), Value::Function(Rc::new(|args: &[Value]| tish_math_sqrt(args)))),");
1339
+ self.writeln("(Arc::from(\"sqrt\"), Value::native(|args: &[Value]| tish_math_sqrt(args))),");
1104
1340
  self.writeln(
1105
- "(Arc::from(\"min\"), Value::Function(Rc::new(|args: &[Value]| tish_math_min(args)))),",
1341
+ "(Arc::from(\"min\"), Value::native(|args: &[Value]| tish_math_min(args))),",
1106
1342
  );
1107
1343
  self.writeln(
1108
- "(Arc::from(\"max\"), Value::Function(Rc::new(|args: &[Value]| tish_math_max(args)))),",
1344
+ "(Arc::from(\"max\"), Value::native(|args: &[Value]| tish_math_max(args))),",
1109
1345
  );
1110
- self.writeln("(Arc::from(\"floor\"), Value::Function(Rc::new(|args: &[Value]| tish_math_floor(args)))),");
1111
- self.writeln("(Arc::from(\"ceil\"), Value::Function(Rc::new(|args: &[Value]| tish_math_ceil(args)))),");
1112
- self.writeln("(Arc::from(\"round\"), Value::Function(Rc::new(|args: &[Value]| tish_math_round(args)))),");
1113
- self.writeln("(Arc::from(\"random\"), Value::Function(Rc::new(|args: &[Value]| tish_math_random(args)))),");
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))),");
1114
1350
  self.writeln(
1115
- "(Arc::from(\"pow\"), Value::Function(Rc::new(|args: &[Value]| tish_math_pow(args)))),",
1351
+ "(Arc::from(\"pow\"), Value::native(|args: &[Value]| tish_math_pow(args))),",
1116
1352
  );
1117
1353
  self.writeln(
1118
- "(Arc::from(\"sin\"), Value::Function(Rc::new(|args: &[Value]| tish_math_sin(args)))),",
1354
+ "(Arc::from(\"sin\"), Value::native(|args: &[Value]| tish_math_sin(args))),",
1119
1355
  );
1120
1356
  self.writeln(
1121
- "(Arc::from(\"cos\"), Value::Function(Rc::new(|args: &[Value]| tish_math_cos(args)))),",
1357
+ "(Arc::from(\"cos\"), Value::native(|args: &[Value]| tish_math_cos(args))),",
1122
1358
  );
1123
1359
  self.writeln(
1124
- "(Arc::from(\"tan\"), Value::Function(Rc::new(|args: &[Value]| tish_math_tan(args)))),",
1360
+ "(Arc::from(\"tan\"), Value::native(|args: &[Value]| tish_math_tan(args))),",
1125
1361
  );
1126
1362
  self.writeln(
1127
- "(Arc::from(\"log\"), Value::Function(Rc::new(|args: &[Value]| tish_math_log(args)))),",
1363
+ "(Arc::from(\"log\"), Value::native(|args: &[Value]| tish_math_log(args))),",
1128
1364
  );
1129
1365
  self.writeln(
1130
- "(Arc::from(\"exp\"), Value::Function(Rc::new(|args: &[Value]| tish_math_exp(args)))),",
1366
+ "(Arc::from(\"exp\"), Value::native(|args: &[Value]| tish_math_exp(args))),",
1131
1367
  );
1132
- self.writeln("(Arc::from(\"sign\"), Value::Function(Rc::new(|args: &[Value]| tish_math_sign(args)))),");
1133
- self.writeln("(Arc::from(\"trunc\"), Value::Function(Rc::new(|args: &[Value]| tish_math_trunc(args)))),");
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))),");
1134
1370
  self.writeln("(Arc::from(\"PI\"), Value::Number(std::f64::consts::PI)),");
1135
1371
  self.writeln("(Arc::from(\"E\"), Value::Number(std::f64::consts::E)),");
1136
1372
  self.indent -= 1;
1137
- self.writeln("]))));");
1138
- 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([");
1139
1375
  self.indent += 1;
1140
- self.writeln("(Arc::from(\"parse\"), Value::Function(Rc::new(|args: &[Value]| tish_json_parse(args)))),");
1141
- 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))),");
1142
1378
  self.indent -= 1;
1143
- self.writeln("]))));");
1379
+ self.writeln("])));");
1144
1380
 
1145
- self.writeln("let Array = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
1381
+ self.writeln("let Array = Value::Object(VmRef::new(ObjectMap::from([");
1146
1382
  self.indent += 1;
1147
- 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))),");
1148
1384
  self.indent -= 1;
1149
- self.writeln("]))));");
1385
+ self.writeln("])));");
1150
1386
 
1151
- self.writeln("let String = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
1387
+ self.writeln("let String = Value::Object(VmRef::new(ObjectMap::from([");
1152
1388
  self.indent += 1;
1153
- 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))),");
1154
1390
  self.indent -= 1;
1155
- self.writeln("]))));");
1391
+ self.writeln("])));");
1156
1392
 
1157
- self.writeln("let Date = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
1393
+ self.writeln("let Date = Value::Object(VmRef::new(ObjectMap::from([");
1158
1394
  self.indent += 1;
1159
1395
  self.writeln(
1160
- "(Arc::from(\"now\"), Value::Function(Rc::new(|args: &[Value]| tish_date_now(args)))),",
1396
+ "(Arc::from(\"now\"), Value::native(|args: &[Value]| tish_date_now(args))),",
1161
1397
  );
1162
1398
  self.indent -= 1;
1163
- self.writeln("]))));");
1399
+ self.writeln("])));");
1164
1400
 
1165
- self.writeln("let Object = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
1401
+ self.writeln("let Object = Value::Object(VmRef::new(ObjectMap::from([");
1166
1402
  self.indent += 1;
1167
- self.writeln("(Arc::from(\"assign\"), Value::Function(Rc::new(|args: &[Value]| tish_object_assign(args)))),");
1168
- self.writeln("(Arc::from(\"keys\"), Value::Function(Rc::new(|args: &[Value]| tish_object_keys(args)))),");
1169
- self.writeln("(Arc::from(\"values\"), Value::Function(Rc::new(|args: &[Value]| tish_object_values(args)))),");
1170
- self.writeln("(Arc::from(\"entries\"), Value::Function(Rc::new(|args: &[Value]| tish_object_entries(args)))),");
1171
- 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))),");
1172
1408
  self.indent -= 1;
1173
- self.writeln("]))));");
1409
+ self.writeln("])));");
1174
1410
 
1175
1411
  self.writeln("let Uint8Array = tish_uint8_array_constructor();");
1176
1412
  self.writeln("let AudioContext = tish_audio_context_constructor();");
1177
1413
 
1178
1414
  if self.has_feature("process") {
1179
- self.writeln("let process = Value::Object(Rc::new(RefCell::new({");
1415
+ self.writeln("let process = Value::Object(VmRef::new({");
1180
1416
  self.indent += 1;
1181
1417
  self.writeln("let mut p = ObjectMap::default();");
1182
- self.writeln("p.insert(Arc::from(\"exit\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exit(args))));");
1183
- self.writeln("p.insert(Arc::from(\"cwd\"), Value::Function(Rc::new(|args: &[Value]| tish_process_cwd(args))));");
1184
- 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)));");
1185
1421
  self.writeln("let argv: Vec<Value> = std::env::args().map(|s| Value::String(s.into())).collect();");
1186
1422
  self.writeln(
1187
- "p.insert(Arc::from(\"argv\"), Value::Array(Rc::new(RefCell::new(argv))));",
1423
+ "p.insert(Arc::from(\"argv\"), Value::Array(VmRef::new(argv)));",
1188
1424
  );
1189
1425
  self.writeln("let mut env_obj = ObjectMap::default();");
1190
1426
  self.writeln("for (key, value) in std::env::vars() {");
@@ -1193,72 +1429,86 @@ impl Codegen {
1193
1429
  self.indent -= 1;
1194
1430
  self.writeln("}");
1195
1431
  self.writeln(
1196
- "p.insert(Arc::from(\"env\"), Value::Object(Rc::new(RefCell::new(env_obj))));",
1432
+ "p.insert(Arc::from(\"env\"), Value::Object(VmRef::new(env_obj)));",
1197
1433
  );
1198
1434
  self.writeln("p");
1199
1435
  self.indent -= 1;
1200
- self.writeln("})));");
1436
+ self.writeln("}));");
1201
1437
  }
1202
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
+ }
1203
1445
  if self.has_feature("http") {
1204
- self.writeln("let fetch = Value::Function(Rc::new(|args: &[Value]| tish_fetch_promise(args.to_vec())));");
1205
- self.writeln("let fetchAll = Value::Function(Rc::new(|args: &[Value]| tish_fetch_all_promise(args.to_vec())));");
1206
- self.writeln("let setTimeout = Value::Function(Rc::new(|args: &[Value]| tish_timer_set_timeout(args)));");
1207
- 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()));");
1208
1448
  if self.is_async {
1209
1449
  self.writeln("let Promise = tish_promise_object();");
1210
1450
  }
1211
- 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]| {");
1212
1460
  self.indent += 1;
1213
- self.writeln("let port = args.first().cloned().unwrap_or(Value::Null);");
1214
1461
  self.writeln("let handler = args.get(1).cloned().unwrap_or(Value::Null);");
1215
- self.writeln("if let Value::Function(f) = handler {");
1462
+ self.writeln("match handler {");
1216
1463
  self.indent += 1;
1217
- self.writeln("tish_http_serve(args, move |req_args| f(req_args))");
1218
- self.indent -= 1;
1219
- 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) => {");
1220
1466
  self.indent += 1;
1221
- 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,");
1222
1472
  self.indent -= 1;
1223
1473
  self.writeln("}");
1224
1474
  self.indent -= 1;
1225
- self.writeln("}));");
1475
+ self.writeln("});");
1226
1476
  }
1227
1477
 
1228
1478
  if self.has_feature("fs") {
1229
1479
  self.writeln(
1230
- "let readFile = Value::Function(Rc::new(|args: &[Value]| tish_read_file(args)));",
1480
+ "let readFile = Value::native(|args: &[Value]| tish_read_file(args));",
1231
1481
  );
1232
1482
  self.writeln(
1233
- "let writeFile = Value::Function(Rc::new(|args: &[Value]| tish_write_file(args)));",
1483
+ "let writeFile = Value::native(|args: &[Value]| tish_write_file(args));",
1234
1484
  );
1235
- self.writeln("let fileExists = Value::Function(Rc::new(|args: &[Value]| tish_file_exists(args)));");
1485
+ self.writeln("let fileExists = Value::native(|args: &[Value]| tish_file_exists(args));");
1236
1486
  self.writeln(
1237
- "let isDir = Value::Function(Rc::new(|args: &[Value]| tish_is_dir(args)));",
1487
+ "let isDir = Value::native(|args: &[Value]| tish_is_dir(args));",
1238
1488
  );
1239
1489
  self.writeln(
1240
- "let readDir = Value::Function(Rc::new(|args: &[Value]| tish_read_dir(args)));",
1490
+ "let readDir = Value::native(|args: &[Value]| tish_read_dir(args));",
1241
1491
  );
1242
1492
  self.writeln(
1243
- "let mkdir = Value::Function(Rc::new(|args: &[Value]| tish_mkdir(args)));",
1493
+ "let mkdir = Value::native(|args: &[Value]| tish_mkdir(args));",
1244
1494
  );
1245
1495
  }
1246
1496
 
1247
1497
  if self.has_feature("regex") {
1248
1498
  self.writeln(
1249
- "let RegExp = Value::Function(Rc::new(|args: &[Value]| regexp_new(args)));",
1499
+ "let RegExp = Value::native(|args: &[Value]| regexp_new(args));",
1250
1500
  );
1251
1501
  }
1252
1502
 
1253
1503
  if self.program_has_jsx {
1254
1504
  self.writeln("install_thread_local_host(Box::new(HeadlessHost::default()));");
1255
1505
  self.writeln("let Fragment = fragment_value();");
1256
- self.writeln("let h = Value::Function(Rc::new(|args: &[Value]| ui_h(args)));");
1257
- self.writeln("let text = Value::Function(Rc::new(|args: &[Value]| ui_text(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));");
1258
1508
  self.writeln(
1259
- "let useState = Value::Function(Rc::new(|args: &[Value]| native_use_state(args)));",
1509
+ "let useState = Value::native(|args: &[Value]| native_use_state(args));",
1260
1510
  );
1261
- self.writeln("let createRoot = Value::Function(Rc::new(|args: &[Value]| native_create_root(args)));");
1511
+ self.writeln("let createRoot = Value::native(|args: &[Value]| native_create_root(args));");
1262
1512
  }
1263
1513
 
1264
1514
  // Polars, Egui etc. are emitted via VarDecl from import { X } from 'tish:...'
@@ -1269,7 +1519,7 @@ impl Codegen {
1269
1519
  for func_name in &top_level_funcs {
1270
1520
  let escaped = Self::escape_ident(func_name);
1271
1521
  self.writeln(&format!(
1272
- "let {}_cell: Rc<RefCell<Value>> = Rc::new(RefCell::new(Value::Null));",
1522
+ "let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
1273
1523
  escaped
1274
1524
  ));
1275
1525
  }
@@ -1323,7 +1573,7 @@ impl Codegen {
1323
1573
  for func_name in &func_names {
1324
1574
  let escaped = Self::escape_ident(func_name);
1325
1575
  self.writeln(&format!(
1326
- "let {}_cell: Rc<RefCell<Value>> = Rc::new(RefCell::new(Value::Null));",
1576
+ "let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
1327
1577
  escaped
1328
1578
  ));
1329
1579
  }
@@ -1347,10 +1597,15 @@ impl Codegen {
1347
1597
  init,
1348
1598
  ..
1349
1599
  } => {
1350
- // Determine the Rust type from annotation
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.
1351
1604
  let rust_type = type_ann
1352
1605
  .as_ref()
1353
- .map(RustType::from_annotation)
1606
+ .map(|t| {
1607
+ crate::types::RustType::from_annotation_with_aliases(t, &self.type_aliases)
1608
+ })
1354
1609
  .unwrap_or(RustType::Value);
1355
1610
 
1356
1611
  // Track the variable type
@@ -1368,7 +1623,7 @@ impl Codegen {
1368
1623
  if self.refcell_wrapped_vars.contains(name.as_ref()) {
1369
1624
  // Closure-mutated: same Rc<RefCell<T>> pattern as Value (assignments use borrow_mut)
1370
1625
  self.writeln(&format!(
1371
- "let {} = std::rc::Rc::new(RefCell::new({}));",
1626
+ "let {} = VmRef::new({});",
1372
1627
  escaped_name, expr_str
1373
1628
  ));
1374
1629
  self.rc_cell_storage_define(name.as_ref());
@@ -1399,7 +1654,7 @@ impl Codegen {
1399
1654
  expr_str.to_string()
1400
1655
  };
1401
1656
  self.writeln(&format!(
1402
- "let {} = std::rc::Rc::new(RefCell::new({}));",
1657
+ "let {} = VmRef::new({});",
1403
1658
  escaped_name, init_val
1404
1659
  ));
1405
1660
  self.rc_cell_storage_define(name.as_ref());
@@ -1433,6 +1688,7 @@ impl Codegen {
1433
1688
  };
1434
1689
  self.writeln(&format!("let _destruct_val = ({}){};", expr, clone_suffix));
1435
1690
  self.emit_destruct_bindings(pattern, "_destruct_val", mutability, *span)?;
1691
+ self.register_destruct_pattern_outer_vars(pattern);
1436
1692
  }
1437
1693
  Statement::ExprStmt { expr, .. } => {
1438
1694
  let e = self.emit_expr(expr)?;
@@ -1584,6 +1840,9 @@ impl Codegen {
1584
1840
  span: None,
1585
1841
  });
1586
1842
  }
1843
+ Statement::TypeAlias { .. }
1844
+ | Statement::DeclareVar { .. }
1845
+ | Statement::DeclareFun { .. } => {}
1587
1846
  Statement::Switch {
1588
1847
  expr,
1589
1848
  cases,
@@ -1637,10 +1896,17 @@ impl Codegen {
1637
1896
  }
1638
1897
  Statement::Throw { value, .. } => {
1639
1898
  let v = self.emit_expr(value)?;
1640
- self.writeln(&format!(
1641
- "return Err(Box::new(tishlang_runtime::TishError::Throw({})) as Box<dyn std::error::Error>);",
1642
- v
1643
- ));
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
+ }
1644
1910
  }
1645
1911
  Statement::Try {
1646
1912
  body,
@@ -1670,10 +1936,22 @@ impl Codegen {
1670
1936
  Self::escape_ident(param.as_ref())
1671
1937
  ));
1672
1938
  self.emit_statement(catch_stmt)?;
1673
- 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
+ }
1674
1946
  self.indent -= 1;
1675
1947
  self.writeln("}");
1676
- 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
+ }
1677
1955
  self.indent -= 1;
1678
1956
  self.writeln("}");
1679
1957
  self.indent -= 1;
@@ -1710,7 +1988,7 @@ impl Codegen {
1710
1988
  .unwrap_or(false);
1711
1989
  if !cell_exists {
1712
1990
  self.writeln(&format!(
1713
- "let {}_cell: Rc<RefCell<Value>> = Rc::new(RefCell::new(Value::Null));",
1991
+ "let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
1714
1992
  name_str
1715
1993
  ));
1716
1994
  }
@@ -1751,9 +2029,12 @@ impl Codegen {
1751
2029
  "Math",
1752
2030
  "JSON",
1753
2031
  "Date",
2032
+ "Object",
1754
2033
  "process",
1755
2034
  "setTimeout",
1756
2035
  "clearTimeout",
2036
+ "setInterval",
2037
+ "clearInterval",
1757
2038
  "Promise",
1758
2039
  "RegExp",
1759
2040
  "Polars",
@@ -1788,7 +2069,7 @@ impl Codegen {
1788
2069
  ));
1789
2070
  } else {
1790
2071
  self.writeln(&format!(
1791
- "let {}_cell = std::rc::Rc::new(RefCell::new({}.clone()));",
2072
+ "let {}_cell = VmRef::new({}.clone());",
1792
2073
  var_escaped, var_escaped
1793
2074
  ));
1794
2075
  }
@@ -1846,20 +2127,40 @@ impl Codegen {
1846
2127
  "Math",
1847
2128
  "JSON",
1848
2129
  "Date",
2130
+ "Object",
1849
2131
  "Uint8Array",
1850
2132
  "AudioContext",
1851
2133
  "process",
1852
2134
  "setTimeout",
1853
2135
  "clearTimeout",
2136
+ "setInterval",
2137
+ "clearInterval",
1854
2138
  "Promise",
1855
2139
  "RegExp",
1856
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",
1857
2157
  ] {
1858
2158
  if referenced.contains(*builtin) {
1859
2159
  self.writeln(&format!("let {} = {}.clone();", builtin, builtin));
1860
2160
  }
1861
2161
  }
1862
- self.writeln("Value::Function(Rc::new(move |args: &[Value]| {");
2162
+ self.writeln("Value::native(move |args: &[Value]| {");
2163
+ self.value_fn_depth += 1;
1863
2164
  self.indent += 1;
1864
2165
  // Mutable outer vars: capture the RefCell so assignments use borrow_mut
1865
2166
  for outer_var in &mutable_outer_vars {
@@ -1920,57 +2221,81 @@ impl Codegen {
1920
2221
  }
1921
2222
  if let Some(rest) = rest_param {
1922
2223
  self.writeln(&format!(
1923
- "let {} = Value::Array(std::rc::Rc::new(RefCell::new(args[{}..].to_vec())));",
2224
+ "let {} = Value::Array(VmRef::new(args[{}..].to_vec()));",
1924
2225
  Self::escape_ident(rest.name.as_ref()),
1925
2226
  params.len()
1926
2227
  ));
1927
2228
  }
1928
2229
 
1929
- // Push current params to stack for nested functions
1930
- self.outer_params_stack.push(current_param_names);
2230
+ self.type_context
2231
+ .push_fun_param_scope(params, rest_param.as_ref());
1931
2232
 
1932
- // Function bodies are sync closures (even Tish async fn) - use block_on for await
1933
- self.async_context_stack.push(false);
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);
1934
2236
 
1935
- // Mutable outer vars must be in refcell_wrapped_vars so Assign/CompoundAssign emit borrow_mut
1936
- let saved_refcell = self.refcell_wrapped_vars.clone();
1937
- for v in &mutable_outer_vars {
1938
- self.refcell_wrapped_vars.insert(v.clone());
1939
- }
2237
+ // Function bodies are sync closures (even Tish async fn) - use block_on for await
2238
+ self.async_context_stack.push(false);
1940
2239
 
1941
- // Pre-scan body for nested functions (handles function body as Block)
1942
- if let Statement::Block { statements, .. } = body.as_ref() {
1943
- let nested_func_names = self.prescan_function_decls(statements);
1944
- self.function_scope_stack.push(nested_func_names.clone());
1945
- // Create cells for nested functions
1946
- for func_name in &nested_func_names {
1947
- let escaped = Self::escape_ident(func_name);
1948
- self.writeln(&format!(
1949
- "let {}_cell: Rc<RefCell<Value>> = Rc::new(RefCell::new(Value::Null));",
1950
- escaped
1951
- ));
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());
1952
2244
  }
1953
- for s in statements {
1954
- self.emit_statement(s)?;
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();
1955
2276
  }
1956
- self.function_scope_stack.pop();
1957
- } else {
1958
- self.function_scope_stack.push(Vec::new());
1959
- self.emit_statement(body)?;
1960
- self.function_scope_stack.pop();
1961
- }
1962
2277
 
1963
- self.async_context_stack.pop();
2278
+ self.async_context_stack.pop();
1964
2279
 
1965
- // Restore refcell_wrapped_vars (remove mutable outer vars we added)
1966
- self.refcell_wrapped_vars = saved_refcell;
2280
+ // Restore refcell_wrapped_vars (remove mutable outer vars we added)
2281
+ self.refcell_wrapped_vars = saved_refcell;
1967
2282
 
1968
- // Pop params stack
1969
- self.outer_params_stack.pop();
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);
2293
+ }
1970
2294
 
1971
2295
  self.writeln("Value::Null");
1972
2296
  self.indent -= 1;
1973
- self.writeln("}))");
2297
+ self.writeln("})");
2298
+ self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
1974
2299
  self.indent -= 1;
1975
2300
  self.writeln("};");
1976
2301
  // Update the cell with the actual function value
@@ -2048,7 +2373,7 @@ impl Codegen {
2048
2373
  for (i, elem) in elements.iter().enumerate() {
2049
2374
  if let Some(el) = elem {
2050
2375
  match el {
2051
- DestructElement::Ident(name) => {
2376
+ DestructElement::Ident(name, _) => {
2052
2377
  self.writeln(&format!(
2053
2378
  "{} {} = match &({}) {{ Value::Array(ref _a) => _a.borrow().get({}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
2054
2379
  mutability,
@@ -2065,9 +2390,9 @@ impl Codegen {
2065
2390
  ));
2066
2391
  self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
2067
2392
  }
2068
- DestructElement::Rest(name) => {
2393
+ DestructElement::Rest(name, _) => {
2069
2394
  self.writeln(&format!(
2070
- "{} {} = 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())) }};",
2071
2396
  mutability,
2072
2397
  Self::escape_ident(name.as_ref()),
2073
2398
  value_expr,
@@ -2082,7 +2407,7 @@ impl Codegen {
2082
2407
  for prop in props {
2083
2408
  let key = prop.key.as_ref();
2084
2409
  match &prop.value {
2085
- DestructElement::Ident(name) => {
2410
+ DestructElement::Ident(name, _) => {
2086
2411
  self.writeln(&format!(
2087
2412
  "{} {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
2088
2413
  mutability,
@@ -2099,7 +2424,7 @@ impl Codegen {
2099
2424
  ));
2100
2425
  self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
2101
2426
  }
2102
- DestructElement::Rest(_) => {
2427
+ DestructElement::Rest(_, _) => {
2103
2428
  return Err(CompileError::new(
2104
2429
  "Rest in object destructuring not supported",
2105
2430
  Some(span),
@@ -2112,6 +2437,47 @@ impl Codegen {
2112
2437
  Ok(())
2113
2438
  }
2114
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
+
2115
2481
  fn emit_expr(&mut self, expr: &Expr) -> Result<String, CompileError> {
2116
2482
  Ok(match expr {
2117
2483
  Expr::Literal { value, .. } => match value {
@@ -2165,6 +2531,51 @@ impl Codegen {
2165
2531
  }
2166
2532
  }
2167
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
+
2168
2579
  // Compile-time embed: Polars.read_csv("<literal path>") when file exists
2169
2580
  if let Some(init) = self.native_module_init.get("tish:polars") {
2170
2581
  let crate_name = match init {
@@ -2180,7 +2591,7 @@ impl Codegen {
2180
2591
  {
2181
2592
  if let Expr::Member {
2182
2593
  object,
2183
- prop: MemberProp::Name(ref method_name),
2594
+ prop: MemberProp::Name { name: ref method_name, .. },
2184
2595
  ..
2185
2596
  } = callee.as_ref()
2186
2597
  {
@@ -2211,7 +2622,12 @@ impl Codegen {
2211
2622
  }
2212
2623
 
2213
2624
  // Check for built-in method calls on arrays/strings
2214
- 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
+ {
2215
2631
  // ── native Vec<T> push fast path ──────────────────────────────
2216
2632
  if method_name.as_ref() == "push" {
2217
2633
  if let Expr::Ident { name, .. } = object.as_ref() {
@@ -2594,9 +3010,43 @@ impl Codegen {
2594
3010
  optional,
2595
3011
  ..
2596
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
+ }
2597
3047
  let obj = self.emit_expr(object)?;
2598
3048
  let key = match prop {
2599
- MemberProp::Name(n) => format!("{:?}", n.as_ref()),
3049
+ MemberProp::Name { name, .. } => format!("{:?}", name.as_ref()),
2600
3050
  MemberProp::Expr(e) => {
2601
3051
  let k = self.emit_expr(e)?;
2602
3052
  format!("{}.to_display_string()", k)
@@ -2670,7 +3120,7 @@ impl Codegen {
2670
3120
  }
2671
3121
  }
2672
3122
  }
2673
- 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(" "))
2674
3124
  } else {
2675
3125
  let mut els = Vec::new();
2676
3126
  for elem in elements {
@@ -2689,7 +3139,7 @@ impl Codegen {
2689
3139
  }
2690
3140
  }
2691
3141
  format!(
2692
- "Value::Array(Rc::new(RefCell::new(vec![{}])))",
3142
+ "Value::Array(VmRef::new(vec![{}]))",
2693
3143
  els.join(", ")
2694
3144
  )
2695
3145
  }
@@ -2714,7 +3164,7 @@ impl Codegen {
2714
3164
  }
2715
3165
  }
2716
3166
  }
2717
- 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(" "))
2718
3168
  } else {
2719
3169
  let mut parts = Vec::new();
2720
3170
  for prop in props {
@@ -2728,7 +3178,7 @@ impl Codegen {
2728
3178
  }
2729
3179
  }
2730
3180
  format!(
2731
- "Value::Object(Rc::new(RefCell::new(ObjectMap::from([{}]))))",
3181
+ "Value::Object(VmRef::new(ObjectMap::from([{}])))",
2732
3182
  parts.join(", ")
2733
3183
  )
2734
3184
  }
@@ -3149,9 +3599,12 @@ impl Codegen {
3149
3599
  format!("Value::String([{}].concat().into())", parts.join(", "))
3150
3600
  }
3151
3601
  Expr::JsxElement { .. } | Expr::JsxFragment { .. } => {
3152
- tishlang_ui::jsx::emit_jsx_rust(expr, &mut |e| {
3153
- self.emit_expr(e).map_err(|ce| ce.message)
3154
- })
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
+ )
3155
3608
  .map_err(|m| CompileError::new(m, None))?
3156
3609
  }
3157
3610
  Expr::New { callee, args, .. } => {
@@ -3448,7 +3901,10 @@ impl Codegen {
3448
3901
  Statement::Break { .. }
3449
3902
  | Statement::Continue { .. }
3450
3903
  | Statement::Import { .. }
3451
- | Statement::Export { .. } => {}
3904
+ | Statement::Export { .. }
3905
+ | Statement::TypeAlias { .. }
3906
+ | Statement::DeclareVar { .. }
3907
+ | Statement::DeclareFun { .. } => {}
3452
3908
  }
3453
3909
  }
3454
3910
 
@@ -3964,7 +4420,7 @@ impl Codegen {
3964
4420
  match pattern {
3965
4421
  DestructPattern::Array(elements) => {
3966
4422
  for el in elements {
3967
- if let Some(DestructElement::Ident(n)) = el {
4423
+ if let Some(DestructElement::Ident(n, _)) = el {
3968
4424
  names.insert(n.to_string());
3969
4425
  }
3970
4426
  if let Some(DestructElement::Pattern(p)) = el {
@@ -3975,11 +4431,11 @@ impl Codegen {
3975
4431
  DestructPattern::Object(props) => {
3976
4432
  for prop in props {
3977
4433
  match &prop.value {
3978
- DestructElement::Ident(n) => {
4434
+ DestructElement::Ident(n, _) => {
3979
4435
  names.insert(n.to_string());
3980
4436
  }
3981
4437
  DestructElement::Pattern(p) => Self::collect_destruct_names(p, names),
3982
- DestructElement::Rest(n) => {
4438
+ DestructElement::Rest(n, _) => {
3983
4439
  names.insert(n.to_string());
3984
4440
  }
3985
4441
  }
@@ -4085,7 +4541,10 @@ impl Codegen {
4085
4541
  Statement::Break { .. }
4086
4542
  | Statement::Continue { .. }
4087
4543
  | Statement::Import { .. }
4088
- | Statement::Export { .. } => {}
4544
+ | Statement::Export { .. }
4545
+ | Statement::TypeAlias { .. }
4546
+ | Statement::DeclareVar { .. }
4547
+ | Statement::DeclareFun { .. } => {}
4089
4548
  }
4090
4549
  }
4091
4550
 
@@ -4133,6 +4592,25 @@ impl Codegen {
4133
4592
  && !param_names.contains(name)
4134
4593
  && !local_var_names.contains(name)
4135
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
+ })
4136
4614
  .collect();
4137
4615
 
4138
4616
  // Outer vars that are assigned in the body need RefCell; read-only get Value binding
@@ -4141,14 +4619,16 @@ impl Codegen {
4141
4619
  ArrowBody::Expr(e) => Self::collect_assigned_idents_in_expr(e, &mut assigned_in_body),
4142
4620
  ArrowBody::Block(s) => Self::collect_assigned_idents_in_stmt(s, &mut assigned_in_body),
4143
4621
  }
4144
- let mutable_outer_vars: Vec<String> = outer_vars
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
4145
4625
  .iter()
4146
- .filter(|v| assigned_in_body.contains(*v))
4626
+ .filter(|v| assigned_in_body.contains(*v) || self.rc_cell_storage_contains(*v))
4147
4627
  .cloned()
4148
4628
  .collect();
4149
4629
  let read_only_outer_vars: Vec<String> = outer_vars
4150
4630
  .iter()
4151
- .filter(|v| !assigned_in_body.contains(*v))
4631
+ .filter(|v| !assigned_in_body.contains(*v) && !self.rc_cell_storage_contains(*v))
4152
4632
  .cloned()
4153
4633
  .collect();
4154
4634
 
@@ -4164,7 +4644,7 @@ impl Codegen {
4164
4644
  ));
4165
4645
  } else {
4166
4646
  code.push_str(&format!(
4167
- " let {} = std::rc::Rc::new(RefCell::new({}.clone()));\n",
4647
+ " let {} = VmRef::new({}.clone());\n",
4168
4648
  ref_name, param_escaped
4169
4649
  ));
4170
4650
  }
@@ -4179,7 +4659,7 @@ impl Codegen {
4179
4659
  ));
4180
4660
  } else {
4181
4661
  code.push_str(&format!(
4182
- " let {} = std::rc::Rc::new(RefCell::new({}.clone()));\n",
4662
+ " let {} = VmRef::new({}.clone());\n",
4183
4663
  ref_name, var_escaped
4184
4664
  ));
4185
4665
  }
@@ -4190,11 +4670,14 @@ impl Codegen {
4190
4670
  "Math",
4191
4671
  "JSON",
4192
4672
  "Date",
4673
+ "Object",
4193
4674
  "Uint8Array",
4194
4675
  "AudioContext",
4195
4676
  "process",
4196
4677
  "setTimeout",
4197
4678
  "clearTimeout",
4679
+ "setInterval",
4680
+ "clearInterval",
4198
4681
  "Promise",
4199
4682
  "RegExp",
4200
4683
  "Polars",
@@ -4224,7 +4707,8 @@ impl Codegen {
4224
4707
  ));
4225
4708
  }
4226
4709
 
4227
- 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;
4228
4712
 
4229
4713
  // Make captured outer params available as plain Values (from _ref RefCells)
4230
4714
  for outer_param in &outer_params {
@@ -4234,15 +4718,15 @@ impl Codegen {
4234
4718
  param_escaped, param_escaped
4235
4719
  ));
4236
4720
  }
4237
- // Mutable outer vars: capture RefCell so assignments use borrow_mut
4238
- 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 {
4239
4723
  let var_escaped = Self::escape_ident(outer_var);
4240
4724
  code.push_str(&format!(
4241
4725
  " let {} = {}_ref.clone();\n",
4242
4726
  var_escaped, var_escaped
4243
4727
  ));
4244
4728
  }
4245
- // Read-only outer vars: Value binding from borrow
4729
+ // Read-only outer vars: snapshot Value at closure creation
4246
4730
  for outer_var in &read_only_outer_vars {
4247
4731
  let var_escaped = Self::escape_ident(outer_var);
4248
4732
  code.push_str(&format!(
@@ -4297,17 +4781,19 @@ impl Codegen {
4297
4781
  // Push empty scope for variables declared inside this arrow function
4298
4782
  self.outer_vars_stack.push(Vec::new());
4299
4783
 
4300
- // 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
4301
4785
  let saved_refcell_vars = self.refcell_wrapped_vars.clone();
4302
- for v in &mutable_outer_vars {
4786
+ for v in &cell_capture_outer_vars {
4303
4787
  self.refcell_wrapped_vars.insert(v.clone());
4304
4788
  }
4305
4789
 
4306
- // Emit body based on type
4307
- match body {
4790
+ self.type_context.push_fun_param_scope(params, None);
4791
+
4792
+ let arrow_body_res: Result<(), CompileError> = match body {
4308
4793
  tishlang_ast::ArrowBody::Expr(expr) => {
4309
4794
  let expr_code = self.emit_expr(expr)?;
4310
4795
  code.push_str(&format!(" {}\n", expr_code));
4796
+ Ok(())
4311
4797
  }
4312
4798
  tishlang_ast::ArrowBody::Block(block_stmt) => {
4313
4799
  // For block bodies, emit the block statement
@@ -4326,15 +4812,24 @@ impl Codegen {
4326
4812
 
4327
4813
  code.push_str(&body_code);
4328
4814
  code.push_str(" Value::Null\n");
4815
+ Ok(())
4329
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);
4330
4823
  }
4331
4824
 
4825
+ self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
4826
+
4332
4827
  // Restore state
4333
4828
  self.refcell_wrapped_vars = saved_refcell_vars;
4334
4829
  self.outer_params_stack.pop();
4335
4830
  self.outer_vars_stack.pop();
4336
4831
 
4337
- code.push_str(" }))\n");
4832
+ code.push_str(" })\n");
4338
4833
  code.push('}');
4339
4834
 
4340
4835
  Ok(code)
@@ -4386,6 +4881,57 @@ impl Codegen {
4386
4881
  return Ok(format!("vec![{}]", items.join(", ")));
4387
4882
  }
4388
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
+
4389
4935
  // Check if the identifier is already of the target type
4390
4936
  if let Expr::Ident { name, .. } = expr {
4391
4937
  let var_type = self.type_context.get_type(name.as_ref());