@tishlang/tish 1.0.29 → 1.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/Cargo.toml +1 -0
  2. package/crates/js_to_tish/src/transform/expr.rs +15 -6
  3. package/crates/tish/Cargo.toml +1 -1
  4. package/crates/tish/src/main.rs +1 -1
  5. package/crates/tish/tests/integration_test.rs +4 -3
  6. package/crates/tish_ast/src/ast.rs +65 -2
  7. package/crates/tish_build_utils/src/lib.rs +10 -2
  8. package/crates/tish_builtins/src/construct.rs +177 -0
  9. package/crates/tish_builtins/src/globals.rs +3 -5
  10. package/crates/tish_builtins/src/helpers.rs +2 -3
  11. package/crates/tish_builtins/src/lib.rs +1 -0
  12. package/crates/tish_builtins/src/object.rs +3 -4
  13. package/crates/tish_bytecode/src/compiler.rs +85 -11
  14. package/crates/tish_bytecode/src/opcode.rs +7 -3
  15. package/crates/tish_compile/Cargo.toml +1 -0
  16. package/crates/tish_compile/src/codegen.rs +604 -106
  17. package/crates/tish_compile/src/infer.rs +236 -0
  18. package/crates/tish_compile/src/lib.rs +52 -5
  19. package/crates/tish_compile/src/types.rs +42 -5
  20. package/crates/tish_compile_js/Cargo.toml +1 -0
  21. package/crates/tish_compile_js/src/codegen.rs +38 -94
  22. package/crates/tish_compile_js/src/lib.rs +0 -1
  23. package/crates/tish_compile_js/src/tests_jsx.rs +68 -0
  24. package/crates/tish_core/Cargo.toml +4 -0
  25. package/crates/tish_core/src/console_style.rs +7 -1
  26. package/crates/tish_core/src/json.rs +1 -2
  27. package/crates/tish_core/src/macros.rs +2 -3
  28. package/crates/tish_core/src/value.rs +13 -5
  29. package/crates/tish_cranelift/src/lib.rs +6 -4
  30. package/crates/tish_cranelift/src/lower.rs +5 -3
  31. package/crates/tish_cranelift_runtime/src/lib.rs +4 -2
  32. package/crates/tish_eval/Cargo.toml +2 -0
  33. package/crates/tish_eval/src/eval.rs +172 -79
  34. package/crates/tish_eval/src/http.rs +3 -4
  35. package/crates/tish_eval/src/lib.rs +7 -0
  36. package/crates/tish_eval/src/regex.rs +3 -2
  37. package/crates/tish_eval/src/value.rs +11 -13
  38. package/crates/tish_eval/src/value_convert.rs +4 -8
  39. package/crates/tish_fmt/src/lib.rs +49 -10
  40. package/crates/tish_lexer/src/token.rs +2 -0
  41. package/crates/tish_lint/src/lib.rs +9 -0
  42. package/crates/tish_llvm/src/lib.rs +4 -4
  43. package/crates/tish_lsp/README.md +1 -1
  44. package/crates/tish_native/src/build.rs +16 -2
  45. package/crates/tish_native/src/lib.rs +10 -11
  46. package/crates/tish_opt/src/lib.rs +15 -0
  47. package/crates/tish_parser/src/lib.rs +101 -1
  48. package/crates/tish_parser/src/parser.rs +168 -51
  49. package/crates/tish_runtime/src/http.rs +4 -5
  50. package/crates/tish_runtime/src/http_fetch.rs +17 -10
  51. package/crates/tish_runtime/src/lib.rs +9 -2
  52. package/crates/tish_runtime/src/promise.rs +2 -3
  53. package/crates/tish_runtime/src/promise_io.rs +2 -3
  54. package/crates/tish_runtime/src/ws.rs +7 -7
  55. package/crates/tish_ui/Cargo.toml +17 -0
  56. package/crates/tish_ui/src/jsx.rs +390 -0
  57. package/crates/tish_ui/src/lib.rs +16 -0
  58. package/crates/tish_ui/src/runtime/hooks.rs +122 -0
  59. package/crates/tish_ui/src/runtime/mod.rs +173 -0
  60. package/crates/tish_vm/src/vm.rs +121 -27
  61. package/justfile +3 -3
  62. package/package.json +1 -1
  63. package/platform/darwin-arm64/tish +0 -0
  64. package/platform/darwin-x64/tish +0 -0
  65. package/platform/linux-arm64/tish +0 -0
  66. package/platform/linux-x64/tish +0 -0
  67. package/platform/win32-x64/tish.exe +0 -0
  68. package/crates/tish_compile_js/src/js_intrinsics.rs +0 -82
@@ -3,7 +3,7 @@
3
3
  use std::borrow::Cow;
4
4
  use std::collections::{HashMap, HashSet};
5
5
  use std::path::Path;
6
- use tishlang_ast::{ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern, Expr, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement, UnaryOp};
6
+ use tishlang_ast::{ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern, Expr, FunParam, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement, UnaryOp};
7
7
  use crate::resolve::is_builtin_native_spec;
8
8
  use crate::types::{RustType, TypeContext};
9
9
 
@@ -120,6 +120,14 @@ impl UsageAnalyzer {
120
120
  }
121
121
  }
122
122
  }
123
+ Expr::New { callee, args, .. } => {
124
+ self.analyze_expr(callee);
125
+ for arg in args {
126
+ match arg {
127
+ CallArg::Expr(e) | CallArg::Spread(e) => self.analyze_expr(e),
128
+ }
129
+ }
130
+ }
123
131
  Expr::Member { object, prop, .. } => {
124
132
  self.analyze_expr(object);
125
133
  if let MemberProp::Expr(e) = prop {
@@ -283,6 +291,11 @@ fn program_uses_async(program: &Program) -> bool {
283
291
  CallArg::Expr(e) | CallArg::Spread(e) => expr_has_await(e),
284
292
  })
285
293
  }
294
+ 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
+ })
298
+ }
286
299
  Expr::Member { object, prop, .. } => {
287
300
  expr_has_await(object)
288
301
  || if let MemberProp::Expr(e) = prop {
@@ -430,6 +443,9 @@ pub fn compile_with_native_modules(
430
443
  optimize: bool,
431
444
  ) -> Result<String, CompileError> {
432
445
  let program = if optimize { tishlang_opt::optimize(program) } else { program.clone() };
446
+ // Type-inference pass: fills in `type_ann` on unannotated VarDecl nodes where
447
+ // the type is unambiguous (literals, arithmetic of typed vars, etc.).
448
+ let program = crate::infer::infer_program(&program);
433
449
  let map: std::collections::HashMap<String, (String, String)> = native_modules
434
450
  .iter()
435
451
  .map(|m| (m.spec.clone(), (m.crate_name.clone(), m.export_fn.clone())))
@@ -468,6 +484,8 @@ struct Codegen {
468
484
  usage_analyzer: Option<UsageAnalyzer>,
469
485
  /// Type context for tracking variable types (for static typing)
470
486
  type_context: TypeContext,
487
+ /// Program uses JSX; emit `tishlang_ui` imports and `h` / `Fragment` globals.
488
+ program_has_jsx: bool,
471
489
  }
472
490
 
473
491
  impl Codegen {
@@ -493,6 +511,7 @@ impl Codegen {
493
511
  refcell_wrapped_vars: std::collections::HashSet::new(),
494
512
  usage_analyzer: None,
495
513
  type_context: TypeContext::new(),
514
+ program_has_jsx: false,
496
515
  }
497
516
  }
498
517
 
@@ -503,7 +522,12 @@ impl Codegen {
503
522
  return self.builtin_native_module_rust_init(spec, export_name);
504
523
  }
505
524
  self.native_module_map.get(spec).map(|(crate_name, export_fn)| {
506
- format!("{}::{}()", crate_name, export_fn)
525
+ // Native modules return a namespace object (like an ES module).
526
+ // Named imports extract the field from that namespace: `import { foo } from "pkg"` → `ns.foo`.
527
+ format!(
528
+ "{{ let _ns = {}::{}(); match _ns {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }} }}",
529
+ crate_name, export_fn, export_name
530
+ )
507
531
  })
508
532
  }
509
533
 
@@ -536,7 +560,7 @@ impl Codegen {
536
560
  "exec" => Some("Value::Function(Rc::new(|args: &[Value]| tish_process_exec(args)))"),
537
561
  "argv" => Some("Value::Array(Rc::new(RefCell::new(std::env::args().map(|s| Value::String(s.into())).collect())))"),
538
562
  "env" => Some("Value::Object(Rc::new(RefCell::new(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect())))"),
539
- "process" => Some("{ let mut m = HashMap::new(); 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())))); Value::Object(Rc::new(RefCell::new(m))) }"),
563
+ "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))) }"),
540
564
  _ => None,
541
565
  },
542
566
  "tish:ws" if self.has_feature("ws") => match export_name {
@@ -634,6 +658,7 @@ impl Codegen {
634
658
  | Expr::Array { .. }
635
659
  | Expr::Object { .. }
636
660
  | Expr::Call { .. }
661
+ | Expr::New { .. }
637
662
  | Expr::Await { .. }
638
663
  | Expr::ArrowFunction { .. }
639
664
  | Expr::Binary { .. }
@@ -684,7 +709,18 @@ impl Codegen {
684
709
  fn emit_inc_dec(&self, name: &str, is_prefix: bool, delta: &str, op_name: &str) -> String {
685
710
  let n = Self::escape_ident(name);
686
711
  let is_wrapped = self.refcell_wrapped_vars.contains(name);
687
-
712
+ let var_type = self.type_context.get_type(name);
713
+
714
+ // Native fast path: f64 variable → avoid boxing/unboxing.
715
+ if !is_wrapped && var_type == RustType::F64 {
716
+ let op_assign = if delta.contains('+') { "+=" } else { "-=" };
717
+ return if is_prefix {
718
+ format!("{{ {n} {op_assign} 1.0_f64; Value::Number({n}) }}")
719
+ } else {
720
+ format!("{{ let _prev = {n}; {n} {op_assign} 1.0_f64; Value::Number(_prev) }}")
721
+ };
722
+ }
723
+
688
724
  if is_prefix {
689
725
  if is_wrapped {
690
726
  format!(
@@ -695,16 +731,14 @@ impl Codegen {
695
731
  "{{ {n} = Value::Number(match &{n} {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); {n}.clone() }}"
696
732
  )
697
733
  }
734
+ } else if is_wrapped {
735
+ format!(
736
+ "{{ let _v = (*{n}.borrow()).clone(); *{n}.borrow_mut() = Value::Number(match &_v {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); _v }}"
737
+ )
698
738
  } else {
699
- if is_wrapped {
700
- format!(
701
- "{{ let _v = (*{n}.borrow()).clone(); *{n}.borrow_mut() = Value::Number(match &_v {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); _v }}"
702
- )
703
- } else {
704
- format!(
705
- "{{ let _v = {n}.clone(); {n} = Value::Number(match &_v {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); _v }}"
706
- )
707
- }
739
+ format!(
740
+ "{{ let _v = {n}.clone(); {n} = Value::Number(match &_v {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); _v }}"
741
+ )
708
742
  }
709
743
  }
710
744
 
@@ -727,12 +761,17 @@ impl Codegen {
727
761
  use tishlang_ast::ArrowBody;
728
762
 
729
763
  if let Expr::ArrowFunction { params, body, .. } = expr {
730
- // Must have exactly 2 params
731
764
  if params.len() != 2 {
732
765
  return None;
733
766
  }
734
- let param_a = params[0].name.as_ref();
735
- let param_b = params[1].name.as_ref();
767
+ let (param_a, param_b) = match (&params[0], &params[1]) {
768
+ (FunParam::Simple(a), FunParam::Simple(b))
769
+ if a.default.is_none() && b.default.is_none() =>
770
+ {
771
+ (a.name.as_ref(), b.name.as_ref())
772
+ }
773
+ _ => return None,
774
+ };
736
775
 
737
776
  // Body must be a single expression that's a subtraction
738
777
  let body_expr = match body {
@@ -763,12 +802,15 @@ impl Codegen {
763
802
 
764
803
  fn emit_program(&mut self, program: &Program) -> Result<(), CompileError> {
765
804
  self.is_async = program_uses_async(program);
805
+ self.program_has_jsx = tishlang_ui::jsx::program_contains_jsx(program);
766
806
  self.write("#![allow(unused, non_snake_case)]\n\n");
767
807
  self.write("use std::cell::RefCell;\n");
768
- self.write("use std::collections::HashMap;\n");
769
808
  self.write("use std::rc::Rc;\n");
770
809
  self.write("use std::sync::Arc;\n");
771
- 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, TishError, Value};\n");
810
+ 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");
811
+ if self.program_has_jsx {
812
+ self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
813
+ }
772
814
  if self.has_feature("process") {
773
815
  self.write("use tishlang_runtime::{process_exit as tish_process_exit, process_cwd as tish_process_cwd, process_exec as tish_process_exec};\n");
774
816
  }
@@ -818,7 +860,7 @@ impl Codegen {
818
860
  self.indent += 1;
819
861
 
820
862
  // Initialize builtins
821
- self.writeln("let mut console = Value::Object(Rc::new(RefCell::new(HashMap::from([");
863
+ self.writeln("let mut console = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
822
864
  self.indent += 1;
823
865
  self.writeln("(Arc::from(\"debug\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_debug(args); Value::Null }))),");
824
866
  self.writeln("(Arc::from(\"info\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_info(args); Value::Null }))),");
@@ -836,7 +878,7 @@ impl Codegen {
836
878
  self.writeln("let isNaN = Value::Function(Rc::new(|args: &[Value]| tish_is_nan(args)));");
837
879
  self.writeln("let Infinity = Value::Number(f64::INFINITY);");
838
880
  self.writeln("let NaN = Value::Number(f64::NAN);");
839
- self.writeln("let Math = Value::Object(Rc::new(RefCell::new(HashMap::from([");
881
+ self.writeln("let Math = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
840
882
  self.indent += 1;
841
883
  self.writeln("(Arc::from(\"abs\"), Value::Function(Rc::new(|args: &[Value]| tish_math_abs(args)))),");
842
884
  self.writeln("(Arc::from(\"sqrt\"), Value::Function(Rc::new(|args: &[Value]| tish_math_sqrt(args)))),");
@@ -858,32 +900,32 @@ impl Codegen {
858
900
  self.writeln("(Arc::from(\"E\"), Value::Number(std::f64::consts::E)),");
859
901
  self.indent -= 1;
860
902
  self.writeln("]))));");
861
- self.writeln("let JSON = Value::Object(Rc::new(RefCell::new(HashMap::from([");
903
+ self.writeln("let JSON = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
862
904
  self.indent += 1;
863
905
  self.writeln("(Arc::from(\"parse\"), Value::Function(Rc::new(|args: &[Value]| tish_json_parse(args)))),");
864
906
  self.writeln("(Arc::from(\"stringify\"), Value::Function(Rc::new(|args: &[Value]| tish_json_stringify(args)))),");
865
907
  self.indent -= 1;
866
908
  self.writeln("]))));");
867
909
 
868
- self.writeln("let Array = Value::Object(Rc::new(RefCell::new(HashMap::from([");
910
+ self.writeln("let Array = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
869
911
  self.indent += 1;
870
912
  self.writeln("(Arc::from(\"isArray\"), Value::Function(Rc::new(|args: &[Value]| tish_array_is_array(args)))),");
871
913
  self.indent -= 1;
872
914
  self.writeln("]))));");
873
915
 
874
- self.writeln("let String = Value::Object(Rc::new(RefCell::new(HashMap::from([");
916
+ self.writeln("let String = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
875
917
  self.indent += 1;
876
918
  self.writeln("(Arc::from(\"fromCharCode\"), Value::Function(Rc::new(|args: &[Value]| tish_string_from_char_code(args)))),");
877
919
  self.indent -= 1;
878
920
  self.writeln("]))));");
879
921
 
880
- self.writeln("let Date = Value::Object(Rc::new(RefCell::new(HashMap::from([");
922
+ self.writeln("let Date = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
881
923
  self.indent += 1;
882
924
  self.writeln("(Arc::from(\"now\"), Value::Function(Rc::new(|args: &[Value]| tish_date_now(args)))),");
883
925
  self.indent -= 1;
884
926
  self.writeln("]))));");
885
927
 
886
- self.writeln("let Object = Value::Object(Rc::new(RefCell::new(HashMap::from([");
928
+ self.writeln("let Object = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
887
929
  self.indent += 1;
888
930
  self.writeln("(Arc::from(\"assign\"), Value::Function(Rc::new(|args: &[Value]| tish_object_assign(args)))),");
889
931
  self.writeln("(Arc::from(\"keys\"), Value::Function(Rc::new(|args: &[Value]| tish_object_keys(args)))),");
@@ -893,16 +935,19 @@ impl Codegen {
893
935
  self.indent -= 1;
894
936
  self.writeln("]))));");
895
937
 
938
+ self.writeln("let Uint8Array = tish_uint8_array_constructor();");
939
+ self.writeln("let AudioContext = tish_audio_context_constructor();");
940
+
896
941
  if self.has_feature("process") {
897
942
  self.writeln("let process = Value::Object(Rc::new(RefCell::new({");
898
943
  self.indent += 1;
899
- self.writeln("let mut p = HashMap::new();");
944
+ self.writeln("let mut p = ObjectMap::default();");
900
945
  self.writeln("p.insert(Arc::from(\"exit\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exit(args))));");
901
946
  self.writeln("p.insert(Arc::from(\"cwd\"), Value::Function(Rc::new(|args: &[Value]| tish_process_cwd(args))));");
902
947
  self.writeln("p.insert(Arc::from(\"exec\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exec(args))));");
903
948
  self.writeln("let argv: Vec<Value> = std::env::args().map(|s| Value::String(s.into())).collect();");
904
949
  self.writeln("p.insert(Arc::from(\"argv\"), Value::Array(Rc::new(RefCell::new(argv))));");
905
- self.writeln("let mut env_obj = HashMap::new();");
950
+ self.writeln("let mut env_obj = ObjectMap::default();");
906
951
  self.writeln("for (key, value) in std::env::vars() {");
907
952
  self.indent += 1;
908
953
  self.writeln("env_obj.insert(Arc::from(key.as_str()), Value::String(value.into()));");
@@ -952,6 +997,15 @@ impl Codegen {
952
997
  self.writeln("let RegExp = Value::Function(Rc::new(|args: &[Value]| regexp_new(args)));");
953
998
  }
954
999
 
1000
+ if self.program_has_jsx {
1001
+ self.writeln("install_thread_local_host(Box::new(HeadlessHost::default()));");
1002
+ self.writeln("let Fragment = fragment_value();");
1003
+ self.writeln("let h = Value::Function(Rc::new(|args: &[Value]| ui_h(args)));");
1004
+ self.writeln("let text = Value::Function(Rc::new(|args: &[Value]| ui_text(args)));");
1005
+ self.writeln("let useState = Value::Function(Rc::new(|args: &[Value]| native_use_state(args)));");
1006
+ self.writeln("let createRoot = Value::Function(Rc::new(|args: &[Value]| native_create_root(args)));");
1007
+ }
1008
+
955
1009
  // Polars, Egui etc. are emitted via VarDecl from import { X } from 'tish:...'
956
1010
 
957
1011
  // Pre-scan for top-level function declarations and create cells (for mutual recursion)
@@ -1078,11 +1132,8 @@ impl Codegen {
1078
1132
  let expr = self.emit_expr(init)?;
1079
1133
  let mutability = if *mutable { "let mut" } else { "let" };
1080
1134
  let clone_suffix = if Self::needs_clone(init) { ".clone()" } else { "" };
1081
- self.writeln(&format!("{{ let _destruct_val = ({}){};", expr, clone_suffix));
1082
- self.indent += 1;
1135
+ self.writeln(&format!("let _destruct_val = ({}){};", expr, clone_suffix));
1083
1136
  self.emit_destruct_bindings(pattern, "_destruct_val", mutability, *span)?;
1084
- self.indent -= 1;
1085
- self.writeln("}");
1086
1137
  }
1087
1138
  Statement::ExprStmt { expr, .. } => {
1088
1139
  let e = self.emit_expr(expr)?;
@@ -1094,8 +1145,8 @@ impl Codegen {
1094
1145
  else_branch,
1095
1146
  ..
1096
1147
  } => {
1097
- let c = self.emit_expr(cond)?;
1098
- self.write(&format!("if {}.is_truthy() {{\n", c));
1148
+ let c = self.emit_cond_expr(cond)?;
1149
+ self.write(&format!("if {} {{\n", c));
1099
1150
  self.indent += 1;
1100
1151
  self.emit_statement(then_branch)?;
1101
1152
  self.indent -= 1;
@@ -1108,11 +1159,11 @@ impl Codegen {
1108
1159
  self.writeln("}");
1109
1160
  }
1110
1161
  Statement::While { cond, body, .. } => {
1111
- let c = self.emit_expr(cond)?;
1162
+ let c = self.emit_cond_expr(cond)?;
1112
1163
  let label = format!("'while_loop_{}", self.loop_label_index);
1113
1164
  self.loop_label_index += 1;
1114
1165
  self.loop_stack.push((label.clone(), None));
1115
- self.write(&format!("{}: while {}.is_truthy() {{\n", label, c));
1166
+ self.write(&format!("{}: while {} {{\n", label, c));
1116
1167
  self.indent += 1;
1117
1168
  self.emit_statement(body)?;
1118
1169
  self.loop_stack.pop();
@@ -1170,7 +1221,7 @@ impl Codegen {
1170
1221
  self.loop_label_index += 1;
1171
1222
  let cond_expr = cond
1172
1223
  .as_ref()
1173
- .map(|c| format!("{}.is_truthy()", self.emit_expr(c).unwrap()))
1224
+ .map(|c| self.emit_cond_expr(c).unwrap())
1174
1225
  .unwrap_or_else(|| "true".to_string());
1175
1226
  let update_code = update.as_ref().map(|u| {
1176
1227
  let ue = self.emit_expr(u).unwrap();
@@ -1262,14 +1313,14 @@ impl Codegen {
1262
1313
  self.writeln("}");
1263
1314
  }
1264
1315
  Statement::DoWhile { body, cond, .. } => {
1265
- let c = self.emit_expr(cond)?;
1316
+ let c = self.emit_cond_expr(cond)?;
1266
1317
  let label = format!("'dowhile_loop_{}", self.loop_label_index);
1267
1318
  self.loop_label_index += 1;
1268
1319
  self.loop_stack.push((label.clone(), None));
1269
1320
  self.write(&format!("{}: loop {{\n", label));
1270
1321
  self.indent += 1;
1271
1322
  self.emit_statement(body)?;
1272
- self.write(&format!("if !{}.is_truthy() {{ break; }}\n", c));
1323
+ self.write(&format!("if !{} {{ break; }}\n", c));
1273
1324
  self.loop_stack.pop();
1274
1325
  self.indent -= 1;
1275
1326
  self.writeln("}");
@@ -1326,7 +1377,7 @@ impl Codegen {
1326
1377
  self.emit_statement(finally_stmt)?;
1327
1378
  }
1328
1379
  }
1329
- Statement::FunDecl { name, params, rest_param, body, .. } => {
1380
+ Statement::FunDecl { name, params, rest_param, body, span, .. } => {
1330
1381
  // Use Rc<RefCell<>> pattern to allow recursive function calls
1331
1382
  // The function can reference itself through the cell
1332
1383
  let name_raw = name.as_ref();
@@ -1343,7 +1394,11 @@ impl Codegen {
1343
1394
  // Analyze body to find which identifiers are actually referenced
1344
1395
  let mut referenced = HashSet::new();
1345
1396
  Self::collect_stmt_idents(body, &mut referenced);
1346
- let param_names: HashSet<String> = params.iter().map(|p| p.name.to_string()).collect();
1397
+ let param_names: HashSet<String> = params
1398
+ .iter()
1399
+ .flat_map(|p| p.bound_names())
1400
+ .map(|n| n.to_string())
1401
+ .collect();
1347
1402
 
1348
1403
  // Collect all outer parameters that need to be captured (only those referenced)
1349
1404
  let outer_params: Vec<String> = self.outer_params_stack
@@ -1418,7 +1473,7 @@ impl Codegen {
1418
1473
  self.writeln(&format!("let {} = {}.clone();", param_escaped, param_escaped));
1419
1474
  }
1420
1475
  // Only clone builtins that are actually referenced (clone so outer scope can still use them, e.g. process for PORT before serve)
1421
- for builtin in &["Boolean", "console", "Math", "JSON", "Date", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"] {
1476
+ for builtin in &["Boolean", "console", "Math", "JSON", "Date", "Uint8Array", "AudioContext", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"] {
1422
1477
  if referenced.contains(*builtin) {
1423
1478
  self.writeln(&format!("let {} = {}.clone();", builtin, builtin));
1424
1479
  }
@@ -1445,13 +1500,30 @@ impl Codegen {
1445
1500
  self.writeln(&format!("let {} = (*{}_ref.borrow()).clone();", sibling_escaped, sibling_escaped));
1446
1501
  }
1447
1502
  // Extract just the parameter names (type annotations are parsed but not used in codegen yet)
1448
- let current_param_names: Vec<String> = params.iter().map(|p| p.name.to_string()).collect();
1503
+ let current_param_names: Vec<String> = params
1504
+ .iter()
1505
+ .flat_map(|p| p.bound_names())
1506
+ .map(|n| n.to_string())
1507
+ .collect();
1508
+ let formal_span = *span;
1449
1509
  for (i, p) in params.iter().enumerate() {
1450
- self.writeln(&format!(
1451
- "let mut {} = args.get({}).cloned().unwrap_or(Value::Null);",
1452
- Self::escape_ident(p.name.as_ref()),
1453
- i
1454
- ));
1510
+ match p {
1511
+ FunParam::Simple(tp) => {
1512
+ self.writeln(&format!(
1513
+ "let mut {} = args.get({}).cloned().unwrap_or(Value::Null);",
1514
+ Self::escape_ident(tp.name.as_ref()),
1515
+ i
1516
+ ));
1517
+ }
1518
+ FunParam::Destructure { pattern, .. } => {
1519
+ let tmp = format!("_formal_{}", i);
1520
+ self.writeln(&format!(
1521
+ "let {} = args.get({}).cloned().unwrap_or(Value::Null);",
1522
+ tmp, i
1523
+ ));
1524
+ self.emit_destruct_bindings(pattern, &tmp, "let mut", formal_span)?;
1525
+ }
1526
+ }
1455
1527
  }
1456
1528
  if let Some(rest) = rest_param {
1457
1529
  self.writeln(&format!(
@@ -1562,58 +1634,71 @@ impl Codegen {
1562
1634
  }
1563
1635
 
1564
1636
  fn emit_destruct_bindings(&mut self, pattern: &DestructPattern, value_expr: &str, mutability: &str, span: Span) -> Result<(), CompileError> {
1637
+ // Flat `let` bindings so names stay in scope for the rest of the function (e.g. JSX).
1565
1638
  match pattern {
1566
1639
  DestructPattern::Array(elements) => {
1567
- self.writeln(&format!("if let Value::Array(ref _arr) = {} {{", value_expr));
1568
- self.indent += 1;
1569
- self.writeln("let _arr_borrow = _arr.borrow();");
1570
1640
  for (i, elem) in elements.iter().enumerate() {
1571
1641
  if let Some(el) = elem {
1572
1642
  match el {
1573
1643
  DestructElement::Ident(name) => {
1574
- self.writeln(&format!("{} {} = _arr_borrow.get({}).cloned().unwrap_or(Value::Null);",
1575
- mutability, Self::escape_ident(name.as_ref()), i));
1644
+ self.writeln(&format!(
1645
+ "{} {} = match &({}) {{ Value::Array(ref _a) => _a.borrow().get({}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
1646
+ mutability,
1647
+ Self::escape_ident(name.as_ref()),
1648
+ value_expr,
1649
+ i
1650
+ ));
1576
1651
  }
1577
1652
  DestructElement::Pattern(nested) => {
1578
- let nested_var = format!("_nested_{}", i);
1579
- self.writeln(&format!("let {} = _arr_borrow.get({}).cloned().unwrap_or(Value::Null);",
1580
- nested_var, i));
1653
+ let nested_var = format!("_nested_arr_{}", i);
1654
+ self.writeln(&format!(
1655
+ "let {} = match &({}) {{ Value::Array(ref _a) => _a.borrow().get({}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
1656
+ nested_var, value_expr, i
1657
+ ));
1581
1658
  self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
1582
1659
  }
1583
1660
  DestructElement::Rest(name) => {
1584
- self.writeln(&format!("{} {} = Value::Array(Rc::new(RefCell::new(_arr_borrow.iter().skip({}).cloned().collect())));",
1585
- mutability, Self::escape_ident(name.as_ref()), i));
1661
+ self.writeln(&format!(
1662
+ "{} {} = 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()))) }};",
1663
+ mutability,
1664
+ Self::escape_ident(name.as_ref()),
1665
+ value_expr,
1666
+ i
1667
+ ));
1586
1668
  }
1587
1669
  }
1588
1670
  }
1589
1671
  }
1590
- self.indent -= 1;
1591
- self.writeln("}");
1592
1672
  }
1593
1673
  DestructPattern::Object(props) => {
1594
- self.writeln(&format!("if let Value::Object(ref _obj) = {} {{", value_expr));
1595
- self.indent += 1;
1596
- self.writeln("let _obj_borrow = _obj.borrow();");
1597
1674
  for prop in props {
1598
1675
  let key = prop.key.as_ref();
1599
1676
  match &prop.value {
1600
1677
  DestructElement::Ident(name) => {
1601
- self.writeln(&format!("{} {} = _obj_borrow.get({:?}).cloned().unwrap_or(Value::Null);",
1602
- mutability, Self::escape_ident(name.as_ref()), key));
1678
+ self.writeln(&format!(
1679
+ "{} {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
1680
+ mutability,
1681
+ Self::escape_ident(name.as_ref()),
1682
+ value_expr,
1683
+ key
1684
+ ));
1603
1685
  }
1604
1686
  DestructElement::Pattern(nested) => {
1605
- let nested_var = format!("_nested_{}", key);
1606
- self.writeln(&format!("let {} = _obj_borrow.get({:?}).cloned().unwrap_or(Value::Null);",
1607
- nested_var, key));
1687
+ let nested_var = format!("_nested_obj_{}", key);
1688
+ self.writeln(&format!(
1689
+ "let {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
1690
+ nested_var, value_expr, key
1691
+ ));
1608
1692
  self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
1609
1693
  }
1610
1694
  DestructElement::Rest(_) => {
1611
- return Err(CompileError::new("Rest in object destructuring not supported", Some(span)));
1695
+ return Err(CompileError::new(
1696
+ "Rest in object destructuring not supported",
1697
+ Some(span),
1698
+ ));
1612
1699
  }
1613
1700
  }
1614
1701
  }
1615
- self.indent -= 1;
1616
- self.writeln("}");
1617
1702
  }
1618
1703
  }
1619
1704
  Ok(())
@@ -1642,10 +1727,10 @@ impl Codegen {
1642
1727
  }
1643
1728
  }
1644
1729
  }
1645
- Expr::Binary { left, op, right, span, .. } => {
1646
- let l = self.emit_expr(left)?;
1647
- let r = self.emit_expr(right)?;
1648
- self.emit_binop(&l, *op, &r, *span)?
1730
+ Expr::Binary { .. } => {
1731
+ // Delegate to emit_typed_expr; wrap the native result in Value.
1732
+ let (code, ty) = self.emit_typed_expr(expr)?;
1733
+ if ty.is_native() { ty.to_value_expr(&code) } else { code }
1649
1734
  }
1650
1735
  Expr::Unary { op, operand, .. } => {
1651
1736
  let o = self.emit_expr(operand)?;
@@ -1706,6 +1791,37 @@ impl Codegen {
1706
1791
 
1707
1792
  // Check for built-in method calls on arrays/strings
1708
1793
  if let Expr::Member { object, prop: MemberProp::Name(method_name), .. } = callee.as_ref() {
1794
+ // ── native Vec<T> push fast path ──────────────────────────────
1795
+ if method_name.as_ref() == "push" {
1796
+ if let Expr::Ident { name, .. } = object.as_ref() {
1797
+ if !self.refcell_wrapped_vars.contains(name.as_ref()) {
1798
+ let obj_type = self.type_context.get_type(name.as_ref());
1799
+ if let RustType::Vec(elem_type) = obj_type {
1800
+ let esc_obj = Self::escape_ident(name.as_ref()).into_owned();
1801
+ // Collect push arguments as native values.
1802
+ let mut push_stmts: Vec<String> = Vec::new();
1803
+ for a in args {
1804
+ if let CallArg::Expr(e) = a {
1805
+ let (val_code, val_ty) = self.emit_typed_expr(e)?;
1806
+ let native_val = if val_ty == *elem_type {
1807
+ val_code
1808
+ } else if val_ty == RustType::Value {
1809
+ elem_type.from_value_expr(&val_code)
1810
+ } else {
1811
+ val_code
1812
+ };
1813
+ push_stmts.push(format!("{}.push({});", esc_obj, native_val));
1814
+ }
1815
+ }
1816
+ return Ok(format!(
1817
+ "{{ {} Value::Null }}",
1818
+ push_stmts.join(" ")
1819
+ ));
1820
+ }
1821
+ }
1822
+ }
1823
+ }
1824
+
1709
1825
  let obj_expr = self.emit_expr(object)?;
1710
1826
  let arg_exprs: Result<Vec<_>, _> =
1711
1827
  args.iter().map(|a| self.emit_call_arg(a)).collect();
@@ -2063,23 +2179,24 @@ impl Codegen {
2063
2179
  format!("tishlang_runtime::get_prop(&{}, {})", obj, key)
2064
2180
  }
2065
2181
  }
2182
+ Expr::Index { optional, .. } if !optional => {
2183
+ // Try native Vec<T> fast path via emit_typed_expr; wrap result.
2184
+ let (code, ty) = self.emit_typed_expr(expr)?;
2185
+ if ty.is_native() { ty.to_value_expr(&code) } else { code }
2186
+ }
2066
2187
  Expr::Index {
2067
2188
  object,
2068
2189
  index,
2069
- optional,
2070
2190
  ..
2071
2191
  } => {
2192
+ // optional chaining: always use runtime path
2072
2193
  let obj = self.emit_expr(object)?;
2073
2194
  let idx = self.emit_expr(index)?;
2074
- if *optional {
2075
- format!(
2076
- "{{ let o = {}.clone(); if matches!(o, Value::Null) {{ Value::Null }} else {{ \
2077
- tishlang_runtime::get_index(&o, &{}) }} }}",
2078
- obj, idx
2079
- )
2080
- } else {
2081
- format!("tishlang_runtime::get_index(&{}, &{})", obj, idx)
2082
- }
2195
+ format!(
2196
+ "{{ let o = {}.clone(); if matches!(o, Value::Null) {{ Value::Null }} else {{ \
2197
+ tishlang_runtime::get_index(&o, &{}) }} }}",
2198
+ obj, idx
2199
+ )
2083
2200
  }
2084
2201
  Expr::Conditional {
2085
2202
  cond,
@@ -2164,7 +2281,7 @@ impl Codegen {
2164
2281
  }
2165
2282
  }
2166
2283
  }
2167
- format!("{{ let mut _obj: HashMap<Arc<str>, Value> = HashMap::new(); {} Value::Object(Rc::new(RefCell::new(_obj))) }}", parts.join(" "))
2284
+ format!("{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::Object(Rc::new(RefCell::new(_obj))) }}", parts.join(" "))
2168
2285
  } else {
2169
2286
  let mut parts = Vec::new();
2170
2287
  for prop in props {
@@ -2178,14 +2295,35 @@ impl Codegen {
2178
2295
  }
2179
2296
  }
2180
2297
  format!(
2181
- "Value::Object(Rc::new(RefCell::new(HashMap::from([{}]))))",
2298
+ "Value::Object(Rc::new(RefCell::new(ObjectMap::from([{}]))))",
2182
2299
  parts.join(", ")
2183
2300
  )
2184
2301
  }
2185
2302
  }
2186
2303
  Expr::Assign { name, value, .. } => {
2187
- let val = self.emit_expr(value)?;
2188
2304
  let escaped = Self::escape_ident(name.as_ref());
2305
+ // Native fast path: if the target is a scalar native type, emit
2306
+ // a direct assignment without boxing/unboxing through Value.
2307
+ if !self.refcell_wrapped_vars.contains(name.as_ref()) {
2308
+ let rust_type = self.type_context.get_type(name.as_ref());
2309
+ if rust_type.is_native() && matches!(rust_type, RustType::F64 | RustType::Bool | RustType::String) {
2310
+ let (val_code, val_ty) = self.emit_typed_expr(value)?;
2311
+ let native_val = if val_ty == rust_type {
2312
+ val_code
2313
+ } else if val_ty == RustType::Value {
2314
+ rust_type.from_value_expr(&val_code)
2315
+ } else {
2316
+ val_code
2317
+ };
2318
+ let return_val = rust_type.to_value_expr(&escaped);
2319
+ return Ok(format!(
2320
+ "{{ {} = {}; {} }}",
2321
+ escaped, native_val, return_val
2322
+ ));
2323
+ }
2324
+ }
2325
+ // Fallback: Value path
2326
+ let val = self.emit_expr(value)?;
2189
2327
  let needs_outer_clone = self.should_clone(value);
2190
2328
  if self.refcell_wrapped_vars.contains(name.as_ref()) {
2191
2329
  if needs_outer_clone {
@@ -2194,7 +2332,6 @@ impl Codegen {
2194
2332
  format!("{{ let _v = {}; *{}.borrow_mut() = _v.clone(); _v }}", val, escaped)
2195
2333
  }
2196
2334
  } else {
2197
- // Use type_context: typed vars need from_value_expr; Value needs .clone() (we return _v)
2198
2335
  let rust_type = self.type_context.get_type(name.as_ref());
2199
2336
  let assign_rhs = if matches!(rust_type, RustType::Value) {
2200
2337
  "_v.clone()".to_string()
@@ -2253,8 +2390,65 @@ impl Codegen {
2253
2390
  Expr::PrefixInc { name, .. } => self.emit_inc_dec(name.as_ref(), true, "+ 1.0", "++"),
2254
2391
  Expr::PrefixDec { name, .. } => self.emit_inc_dec(name.as_ref(), true, "- 1.0", "--"),
2255
2392
  Expr::CompoundAssign { name, op, value, .. } => {
2256
- let val = self.emit_expr(value)?;
2257
2393
  let n = Self::escape_ident(name.as_ref());
2394
+ let is_refcell = self.refcell_wrapped_vars.contains(name.as_ref());
2395
+ let var_type = self.type_context.get_type(name.as_ref());
2396
+
2397
+ // ── native f64 fast path: direct arithmetic operators ─────────
2398
+ // emit_expr must return a Value expression; wrap the result back.
2399
+ if !is_refcell && var_type == RustType::F64 {
2400
+ let (rhs_code, rhs_ty) = self.emit_typed_expr(value)?;
2401
+ let rhs_f64 = if rhs_ty == RustType::F64 {
2402
+ rhs_code
2403
+ } else {
2404
+ // rhs is Value or another native: unbox to f64
2405
+ let rhs_val = if rhs_ty.is_native() {
2406
+ rhs_ty.to_value_expr(&rhs_code)
2407
+ } else {
2408
+ rhs_code
2409
+ };
2410
+ format!("(match &({}) {{ Value::Number(n) => *n, v => panic!(\"compound assign: expected number, got {{:?}}\", v) }})", rhs_val)
2411
+ };
2412
+ let op_str = match op {
2413
+ CompoundOp::Add => "+=",
2414
+ CompoundOp::Sub => "-=",
2415
+ CompoundOp::Mul => "*=",
2416
+ CompoundOp::Div => "/=",
2417
+ CompoundOp::Mod => "%=",
2418
+ };
2419
+ // Wrap in Value::Number so the expression is a valid Value
2420
+ return Ok(format!("{{ {} {} {}; Value::Number({}) }}", n, op_str, rhs_f64, n));
2421
+ }
2422
+
2423
+ // ── native String += fast path: push_str ─────────────────────
2424
+ if !is_refcell && var_type == RustType::String && matches!(op, CompoundOp::Add) {
2425
+ let (rhs_code, rhs_ty) = self.emit_typed_expr(value)?;
2426
+ let rhs_str = if rhs_ty == RustType::String {
2427
+ rhs_code
2428
+ } else {
2429
+ // Convert rhs Value to display string inline
2430
+ let rhs_val = if rhs_ty.is_native() {
2431
+ rhs_ty.to_value_expr(&rhs_code)
2432
+ } else {
2433
+ rhs_code
2434
+ };
2435
+ format!(
2436
+ "match &({}) {{ \
2437
+ Value::String(s) => s.to_string(), \
2438
+ Value::Number(n) => {{ let i = *n as i64; if (*n - i as f64).abs() < f64::EPSILON {{ i.to_string() }} else {{ n.to_string() }} }}, \
2439
+ Value::Bool(b) => b.to_string(), \
2440
+ Value::Null => \"null\".to_string(), \
2441
+ other => format!(\"{{:?}}\", other) }}",
2442
+ rhs_val
2443
+ )
2444
+ };
2445
+ // Wrap in Value::String so the expression is a valid Value
2446
+ return Ok(format!("{{ {}.push_str(&({})); Value::String({}.clone().into()) }}", n, rhs_str, n));
2447
+ }
2448
+
2449
+ // ── fallback: Value path ──────────────────────────────────────
2450
+ // If the variable is native, wrap it as Value before calling ops::
2451
+ let val = self.emit_expr(value)?;
2258
2452
  let op_fn = match op {
2259
2453
  CompoundOp::Add => "add",
2260
2454
  CompoundOp::Sub => "sub",
@@ -2262,11 +2456,20 @@ impl Codegen {
2262
2456
  CompoundOp::Div => "div",
2263
2457
  CompoundOp::Mod => "modulo",
2264
2458
  };
2265
- if self.refcell_wrapped_vars.contains(name.as_ref()) {
2459
+ if is_refcell {
2266
2460
  format!(
2267
2461
  "{{ let _rhs = ({}).clone(); *{}.borrow_mut() = tishlang_runtime::ops::{}(&*{}.borrow(), &_rhs)?; (*{}.borrow()).clone() }}",
2268
2462
  val, n, op_fn, n, n
2269
2463
  )
2464
+ } else if var_type.is_native() {
2465
+ // Wrap native lhs as Value, run ops::, unbox result back to native
2466
+ let n_as_value = var_type.to_value_expr(&n);
2467
+ let result_native = var_type.from_value_expr("_result");
2468
+ let n_as_value2 = var_type.to_value_expr(&n);
2469
+ format!(
2470
+ "{{ let _lhs = {}; let _rhs = ({}).clone(); let _result = tishlang_runtime::ops::{}(&_lhs, &_rhs)?; {} = {}; {} }}",
2471
+ n_as_value, val, op_fn, n, result_native, n_as_value2
2472
+ )
2270
2473
  } else {
2271
2474
  format!(
2272
2475
  "{{ let _rhs = ({}).clone(); {} = tishlang_runtime::ops::{}(&{}, &_rhs)?; {}.clone() }}",
@@ -2278,6 +2481,35 @@ impl Codegen {
2278
2481
  let val = self.emit_expr(value)?;
2279
2482
  let n = Self::escape_ident(name.as_ref()).into_owned();
2280
2483
  let is_refcell = self.refcell_wrapped_vars.contains(name.as_ref());
2484
+ let var_type = self.type_context.get_type(name.as_ref());
2485
+
2486
+ // ── native type: wrap for condition, unbox for assignment ──────
2487
+ if !is_refcell && var_type.is_native() {
2488
+ // n_as_value uses .clone() for String so we don't consume n
2489
+ let n_as_value = var_type.to_value_expr(&n);
2490
+ let val_as_native = var_type.from_value_expr("_v");
2491
+ let (cond, assign_and_return, else_expr) = match op {
2492
+ LogicalAssignOp::AndAnd => (
2493
+ format!("{{ let __chk = {}; __chk.is_truthy() }}", n_as_value),
2494
+ format!("{{ let _v = ({}).clone(); {} = {}; {} }}", val, n, val_as_native, var_type.to_value_expr(&n)),
2495
+ var_type.to_value_expr(&n),
2496
+ ),
2497
+ LogicalAssignOp::OrOr => (
2498
+ format!("!{{ let __chk = {}; __chk.is_truthy() }}", n_as_value),
2499
+ format!("{{ let _v = ({}).clone(); {} = {}; {} }}", val, n, val_as_native, var_type.to_value_expr(&n)),
2500
+ var_type.to_value_expr(&n),
2501
+ ),
2502
+ // Native types (f64, String, bool) are never null — ??= is a no-op
2503
+ LogicalAssignOp::Nullish => (
2504
+ "false".to_string(),
2505
+ var_type.to_value_expr(&n), // unreachable but must type-check
2506
+ var_type.to_value_expr(&n),
2507
+ ),
2508
+ };
2509
+ return Ok(format!("{{ if {} {{ {} }} else {{ {} }} }}", cond, assign_and_return, else_expr));
2510
+ }
2511
+
2512
+ // ── Value / refcell path ──────────────────────────────────────
2281
2513
  let (cond, assign_and_return, else_expr) = if is_refcell {
2282
2514
  match op {
2283
2515
  LogicalAssignOp::AndAnd => (
@@ -2328,6 +2560,43 @@ impl Codegen {
2328
2560
  )
2329
2561
  }
2330
2562
  Expr::IndexAssign { object, index, value, .. } => {
2563
+ // Native fast path: Vec<T>[i] = v
2564
+ if let Expr::Ident { name, .. } = object.as_ref() {
2565
+ if !self.refcell_wrapped_vars.contains(name.as_ref()) {
2566
+ let obj_type = self.type_context.get_type(name.as_ref());
2567
+ if let RustType::Vec(elem_type) = obj_type {
2568
+ let esc_obj = Self::escape_ident(name.as_ref()).into_owned();
2569
+ let (idx_code, idx_ty) = self.emit_typed_expr(index)?;
2570
+ let idx_usize = if idx_ty == RustType::F64 {
2571
+ format!("({}) as usize", idx_code)
2572
+ } else {
2573
+ let iv = if idx_ty.is_native() {
2574
+ idx_ty.to_value_expr(&idx_code)
2575
+ } else {
2576
+ idx_code
2577
+ };
2578
+ format!(
2579
+ "{{ let _i = &{}; if let Value::Number(n) = _i {{ *n as usize }} else {{ panic!(\"array index must be a number\") }} }}",
2580
+ iv
2581
+ )
2582
+ };
2583
+ let (val_code, val_ty) = self.emit_typed_expr(value)?;
2584
+ let native_val = if val_ty == *elem_type {
2585
+ val_code
2586
+ } else if val_ty == RustType::Value {
2587
+ elem_type.from_value_expr(&val_code)
2588
+ } else {
2589
+ // both native but different type — best effort
2590
+ val_code
2591
+ };
2592
+ return Ok(format!(
2593
+ "{{ {}[{}] = {}; Value::Null }}",
2594
+ esc_obj, idx_usize, native_val
2595
+ ));
2596
+ }
2597
+ }
2598
+ }
2599
+ // Fallback: runtime set_index
2331
2600
  let obj = self.emit_expr(object)?;
2332
2601
  let idx = self.emit_expr(index)?;
2333
2602
  let val = self.emit_expr(value)?;
@@ -2338,8 +2607,8 @@ impl Codegen {
2338
2607
  val
2339
2608
  )
2340
2609
  }
2341
- Expr::ArrowFunction { params, body, .. } => {
2342
- self.emit_arrow_function(params, body)?
2610
+ Expr::ArrowFunction { params, body, span, .. } => {
2611
+ self.emit_arrow_function(params, body, *span)?
2343
2612
  }
2344
2613
  Expr::TemplateLiteral { quasis, exprs, .. } => {
2345
2614
  // Build the template string
@@ -2356,7 +2625,33 @@ impl Codegen {
2356
2625
  format!("Value::String([{}].concat().into())", parts.join(", "))
2357
2626
  }
2358
2627
  Expr::JsxElement { .. } | Expr::JsxFragment { .. } => {
2359
- return Err(CompileError { message: "JSX is only supported when compiling to JavaScript (tish compile --target js).".to_string(), span: None });
2628
+ tishlang_ui::jsx::emit_jsx_rust(expr, &mut |e| {
2629
+ self.emit_expr(e).map_err(|ce| ce.message)
2630
+ })
2631
+ .map_err(|m| CompileError::new(m, None))?
2632
+ }
2633
+ Expr::New { callee, args, .. } => {
2634
+ let callee_expr = self.emit_expr(callee)?;
2635
+ let has_spread = args.iter().any(|a| matches!(a, CallArg::Spread(_)));
2636
+ if has_spread {
2637
+ let args_code = self.emit_call_args(args)?;
2638
+ return Ok(format!(
2639
+ "{{ let _callee = ({}).clone(); let _spread_args = {}; tish_construct(&_callee, &_spread_args[..]) }}",
2640
+ callee_expr, args_code
2641
+ ));
2642
+ }
2643
+ let arg_exprs: Result<Vec<_>, _> =
2644
+ args.iter().map(|a| self.emit_call_arg(a)).collect();
2645
+ let arg_exprs = arg_exprs?;
2646
+ let args_vec = arg_exprs
2647
+ .iter()
2648
+ .map(|a| format!("{}.clone()", a))
2649
+ .collect::<Vec<_>>()
2650
+ .join(", ");
2651
+ format!(
2652
+ "tish_construct(&({}).clone(), &[{}])",
2653
+ callee_expr, args_vec
2654
+ )
2360
2655
  }
2361
2656
  Expr::NativeModuleLoad { spec, export_name, .. } => {
2362
2657
  self.native_module_rust_init(spec.as_ref(), export_name.as_ref())
@@ -2410,6 +2705,14 @@ impl Codegen {
2410
2705
  }
2411
2706
  }
2412
2707
  }
2708
+ Expr::New { callee, args, .. } => {
2709
+ Self::collect_expr_idents(callee, idents);
2710
+ for arg in args {
2711
+ match arg {
2712
+ CallArg::Expr(e) | CallArg::Spread(e) => Self::collect_expr_idents(e, idents),
2713
+ }
2714
+ }
2715
+ }
2413
2716
  Expr::Member { object, prop, .. } => {
2414
2717
  Self::collect_expr_idents(object, idents);
2415
2718
  if let MemberProp::Expr(e) = prop { Self::collect_expr_idents(e, idents); }
@@ -2613,6 +2916,16 @@ impl Codegen {
2613
2916
  }
2614
2917
  }
2615
2918
  }
2919
+ Expr::New { callee, args, .. } => {
2920
+ Self::collect_assigned_idents_in_expr(callee, names);
2921
+ for arg in args {
2922
+ match arg {
2923
+ CallArg::Expr(e) | CallArg::Spread(e) => {
2924
+ Self::collect_assigned_idents_in_expr(e, names);
2925
+ }
2926
+ }
2927
+ }
2928
+ }
2616
2929
  Expr::Member { object, prop, .. } => {
2617
2930
  Self::collect_assigned_idents_in_expr(object, names);
2618
2931
  if let MemberProp::Expr(e) = prop {
@@ -2725,12 +3038,16 @@ impl Codegen {
2725
3038
  /// Collect variable names that are both captured and mutated by a closure body.
2726
3039
  /// block_vars: vars declared in the enclosing block (candidates for mutation).
2727
3040
  fn collect_mutated_captures_from_closure(
2728
- params: &[tishlang_ast::TypedParam],
3041
+ params: &[FunParam],
2729
3042
  body: &Statement,
2730
3043
  block_vars: &HashSet<String>,
2731
3044
  result: &mut HashSet<String>,
2732
3045
  ) {
2733
- let param_names: HashSet<String> = params.iter().map(|p| p.name.to_string()).collect();
3046
+ let param_names: HashSet<String> = params
3047
+ .iter()
3048
+ .flat_map(|p| p.bound_names())
3049
+ .map(|n| n.to_string())
3050
+ .collect();
2734
3051
  let mut local_var_names = HashSet::new();
2735
3052
  Self::collect_local_var_names(body, &mut local_var_names);
2736
3053
  let mut referenced = HashSet::new();
@@ -2754,12 +3071,16 @@ impl Codegen {
2754
3071
  }
2755
3072
 
2756
3073
  fn collect_mutated_captures_from_arrow(
2757
- params: &[tishlang_ast::TypedParam],
3074
+ params: &[FunParam],
2758
3075
  body: &ArrowBody,
2759
3076
  block_vars: &HashSet<String>,
2760
3077
  result: &mut HashSet<String>,
2761
3078
  ) {
2762
- let param_names: HashSet<String> = params.iter().map(|p| p.name.to_string()).collect();
3079
+ let param_names: HashSet<String> = params
3080
+ .iter()
3081
+ .flat_map(|p| p.bound_names())
3082
+ .map(|n| n.to_string())
3083
+ .collect();
2763
3084
  let mut local_var_names = HashSet::new();
2764
3085
  match body {
2765
3086
  ArrowBody::Expr(_) => {}
@@ -2808,6 +3129,16 @@ impl Codegen {
2808
3129
  }
2809
3130
  }
2810
3131
  }
3132
+ Expr::New { callee, args, .. } => {
3133
+ Self::collect_mutated_captures_from_expr(callee, block_vars, result);
3134
+ for arg in args {
3135
+ match arg {
3136
+ CallArg::Expr(e) | CallArg::Spread(e) => {
3137
+ Self::collect_mutated_captures_from_expr(e, block_vars, result);
3138
+ }
3139
+ }
3140
+ }
3141
+ }
2811
3142
  Expr::Member { object, prop, .. } => {
2812
3143
  Self::collect_mutated_captures_from_expr(object, block_vars, result);
2813
3144
  if let MemberProp::Expr(e) = prop {
@@ -3055,8 +3386,9 @@ impl Codegen {
3055
3386
 
3056
3387
  fn emit_arrow_function(
3057
3388
  &mut self,
3058
- params: &[tishlang_ast::TypedParam],
3389
+ params: &[FunParam],
3059
3390
  body: &tishlang_ast::ArrowBody,
3391
+ span: Span,
3060
3392
  ) -> Result<String, CompileError> {
3061
3393
  // Build the arrow function as a Value::Function closure
3062
3394
  let mut code = String::new();
@@ -3065,7 +3397,11 @@ impl Codegen {
3065
3397
  // Find which identifiers are actually referenced in the body
3066
3398
  let referenced = Self::collect_referenced_idents(body);
3067
3399
  // Exclude the arrow's own parameters - they're not outer captures
3068
- let param_names: HashSet<String> = params.iter().map(|p| p.name.to_string()).collect();
3400
+ let param_names: HashSet<String> = params
3401
+ .iter()
3402
+ .flat_map(|p| p.bound_names())
3403
+ .map(|n| n.to_string())
3404
+ .collect();
3069
3405
 
3070
3406
  // Exclude variables declared inside the arrow body (locals)
3071
3407
  let mut local_var_names = HashSet::new();
@@ -3120,7 +3456,7 @@ impl Codegen {
3120
3456
  }
3121
3457
  }
3122
3458
  // Only clone builtins that are actually referenced (clone so outer scope can still use, e.g. process for PORT)
3123
- for builtin in &["console", "Math", "JSON", "Date", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"] {
3459
+ for builtin in &["console", "Math", "JSON", "Date", "Uint8Array", "AudioContext", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"] {
3124
3460
  if referenced.contains(*builtin) {
3125
3461
  code.push_str(&format!(" let {} = {}.clone();\n", builtin, builtin));
3126
3462
  }
@@ -3164,13 +3500,35 @@ impl Codegen {
3164
3500
  }
3165
3501
 
3166
3502
  // Extract parameters from args
3167
- let current_param_names: Vec<String> = params.iter().map(|p| p.name.to_string()).collect();
3503
+ let current_param_names: Vec<String> = params
3504
+ .iter()
3505
+ .flat_map(|p| p.bound_names())
3506
+ .map(|n| n.to_string())
3507
+ .collect();
3168
3508
  for (i, p) in params.iter().enumerate() {
3169
- code.push_str(&format!(
3170
- " let mut {} = args.get({}).cloned().unwrap_or(Value::Null);\n",
3171
- Self::escape_ident(p.name.as_ref()),
3172
- i
3173
- ));
3509
+ match p {
3510
+ FunParam::Simple(tp) => {
3511
+ code.push_str(&format!(
3512
+ " let mut {} = args.get({}).cloned().unwrap_or(Value::Null);\n",
3513
+ Self::escape_ident(tp.name.as_ref()),
3514
+ i
3515
+ ));
3516
+ }
3517
+ FunParam::Destructure { pattern, .. } => {
3518
+ let tmp = format!("_formal_{}", i);
3519
+ code.push_str(&format!(
3520
+ " let {} = args.get({}).cloned().unwrap_or(Value::Null);\n",
3521
+ tmp, i
3522
+ ));
3523
+ let saved = std::mem::take(&mut self.output);
3524
+ let saved_indent = self.indent;
3525
+ self.indent = 8;
3526
+ self.emit_destruct_bindings(pattern, &tmp, "let mut", span)?;
3527
+ let frag = std::mem::replace(&mut self.output, saved);
3528
+ self.indent = saved_indent;
3529
+ code.push_str(&frag);
3530
+ }
3531
+ }
3174
3532
  }
3175
3533
 
3176
3534
  // Push current params for potential nested arrows
@@ -3276,6 +3634,146 @@ impl Codegen {
3276
3634
  Ok(target_type.from_value_expr(&value_expr))
3277
3635
  }
3278
3636
 
3637
+ /// Emit an expression and return `(code, type)`.
3638
+ ///
3639
+ /// When `type` is a native type (`F64`, `Bool`, `String`, `Vec<T>`, …), `code`
3640
+ /// evaluates to a Rust value of that type directly — **not** a `Value`.
3641
+ /// When `type` is `RustType::Value`, `code` evaluates to a `Value`.
3642
+ ///
3643
+ /// This is the fast-path used by callers that want to propagate native types
3644
+ /// through arithmetic, indexing, and assignments. For any expression this
3645
+ /// function cannot handle natively, it falls back to `emit_expr` and returns
3646
+ /// `RustType::Value`.
3647
+ fn emit_typed_expr(&mut self, expr: &Expr) -> Result<(String, RustType), CompileError> {
3648
+ match expr {
3649
+ // ── literals ─────────────────────────────────────────────────────────
3650
+ Expr::Literal { value, .. } => match value {
3651
+ Literal::Number(n) => Ok((format!("{}_f64", n), RustType::F64)),
3652
+ Literal::String(s) => Ok((format!("{:?}.to_string()", s.as_ref()), RustType::String)),
3653
+ Literal::Bool(b) => Ok((format!("{}", b), RustType::Bool)),
3654
+ Literal::Null => Ok(("Value::Null".to_string(), RustType::Value)),
3655
+ },
3656
+
3657
+ // ── identifiers ──────────────────────────────────────────────────────
3658
+ Expr::Ident { name, .. } => {
3659
+ let escaped = Self::escape_ident(name.as_ref());
3660
+ if self.refcell_wrapped_vars.contains(name.as_ref()) {
3661
+ // RefCell-wrapped: unwrap via borrow and return Value
3662
+ Ok((format!("(*{}.borrow()).clone()", escaped), RustType::Value))
3663
+ } else {
3664
+ let var_type = self.type_context.get_type(name.as_ref());
3665
+ if var_type.is_native() {
3666
+ Ok((escaped.into_owned(), var_type))
3667
+ } else {
3668
+ Ok((escaped.into_owned(), RustType::Value))
3669
+ }
3670
+ }
3671
+ }
3672
+
3673
+ // ── binary expressions ───────────────────────────────────────────────
3674
+ Expr::Binary { left, op, right, span, .. } => {
3675
+ let (l, lt) = self.emit_typed_expr(left)?;
3676
+ let (r, rt) = self.emit_typed_expr(right)?;
3677
+
3678
+ if let Some(result_ty) = RustType::result_type_of_binop(*op, &lt, &rt) {
3679
+ // Both sides are compatible native types → emit native op.
3680
+ let code = match op {
3681
+ BinOp::Add => format!("({} + {})", l, r),
3682
+ BinOp::Sub => format!("({} - {})", l, r),
3683
+ BinOp::Mul => format!("({} * {})", l, r),
3684
+ BinOp::Div => format!("({} / {})", l, r),
3685
+ BinOp::Mod => format!("({} % {})", l, r),
3686
+ BinOp::Pow => format!("({}).powf({})", l, r),
3687
+ BinOp::Lt => format!("({} < {})", l, r),
3688
+ BinOp::Le => format!("({} <= {})", l, r),
3689
+ BinOp::Gt => format!("({} > {})", l, r),
3690
+ BinOp::Ge => format!("({} >= {})", l, r),
3691
+ BinOp::StrictEq => format!("({} == {})", l, r),
3692
+ BinOp::StrictNe => format!("({} != {})", l, r),
3693
+ BinOp::And => format!("({} && {})", l, r),
3694
+ BinOp::Or => format!("({} || {})", l, r),
3695
+ _ => unreachable!("result_type_of_binop covers all handled ops"),
3696
+ };
3697
+ return Ok((code, result_ty));
3698
+ }
3699
+
3700
+ // Fall back: convert both sides to Value and use the runtime.
3701
+ let lv = if lt.is_native() { lt.to_value_expr(&l) } else { l };
3702
+ let rv = if rt.is_native() { rt.to_value_expr(&r) } else { r };
3703
+ let result = self.emit_binop(&lv, *op, &rv, *span)?;
3704
+ Ok((result, RustType::Value))
3705
+ }
3706
+
3707
+ // ── array indexing ───────────────────────────────────────────────────
3708
+ Expr::Index { object, index, optional, .. } => {
3709
+ // Native fast path: `vec[i]` where vec is Vec<T> and i is numeric.
3710
+ if !optional {
3711
+ if let Expr::Ident { name, .. } = object.as_ref() {
3712
+ if !self.refcell_wrapped_vars.contains(name.as_ref()) {
3713
+ let obj_type = self.type_context.get_type(name.as_ref());
3714
+ if let RustType::Vec(elem_type) = &obj_type {
3715
+ let esc_obj = Self::escape_ident(name.as_ref()).into_owned();
3716
+ let (idx_code, idx_ty) = self.emit_typed_expr(index)?;
3717
+ let idx_usize = if idx_ty == RustType::F64 {
3718
+ format!("({}) as usize", idx_code)
3719
+ } else {
3720
+ let iv = if idx_ty.is_native() {
3721
+ idx_ty.to_value_expr(&idx_code)
3722
+ } else {
3723
+ idx_code
3724
+ };
3725
+ format!(
3726
+ "{{ let _i = &{}; if let Value::Number(n) = _i {{ *n as usize }} else {{ panic!(\"array index must be a number\") }} }}",
3727
+ iv
3728
+ )
3729
+ };
3730
+ let elem_ty = *elem_type.clone();
3731
+ return Ok((format!("{}[{}]", esc_obj, idx_usize), elem_ty));
3732
+ }
3733
+ }
3734
+ }
3735
+ }
3736
+ // Value fallback: emit runtime code directly to avoid cycles
3737
+ // (emit_expr for !optional Index delegates here, so we must not call emit_expr(expr))
3738
+ let obj = self.emit_expr(object)?;
3739
+ let idx = self.emit_expr(index)?;
3740
+ let result = if *optional {
3741
+ format!(
3742
+ "{{ let o = {}.clone(); if matches!(o, Value::Null) {{ Value::Null }} else {{ \
3743
+ tishlang_runtime::get_index(&o, &{}) }} }}",
3744
+ obj, idx
3745
+ )
3746
+ } else {
3747
+ format!("tishlang_runtime::get_index(&{}, &{})", obj, idx)
3748
+ };
3749
+ Ok((result, RustType::Value))
3750
+ }
3751
+
3752
+ // ── everything else: delegate to emit_expr ───────────────────────────
3753
+ _ => {
3754
+ let result = self.emit_expr(expr)?;
3755
+ Ok((result, RustType::Value))
3756
+ }
3757
+ }
3758
+ }
3759
+
3760
+ /// Emit a condition expression as a Rust `bool`.
3761
+ ///
3762
+ /// Returns a `bool`-typed Rust expression when the condition can be
3763
+ /// determined to be native (e.g. `i < N` where both are `f64`), otherwise
3764
+ /// falls back to `{value}.is_truthy()`.
3765
+ fn emit_cond_expr(&mut self, expr: &Expr) -> Result<String, CompileError> {
3766
+ let (code, ty) = self.emit_typed_expr(expr)?;
3767
+ if ty == RustType::Bool {
3768
+ Ok(code)
3769
+ } else if ty.is_native() {
3770
+ // Non-bool native type: convert to Value and use is_truthy
3771
+ Ok(format!("{}.is_truthy()", ty.to_value_expr(&code)))
3772
+ } else {
3773
+ Ok(format!("{}.is_truthy()", code))
3774
+ }
3775
+ }
3776
+
3279
3777
  fn emit_binop(
3280
3778
  &self,
3281
3779
  l: &str,