@tishlang/tish 1.0.28 → 1.0.33

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 (65) 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 +8 -55
  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 +233 -71
  17. package/crates/tish_compile/src/lib.rs +35 -0
  18. package/crates/tish_compile_js/Cargo.toml +1 -1
  19. package/crates/tish_compile_js/src/codegen.rs +43 -147
  20. package/crates/tish_compile_js/src/lib.rs +4 -7
  21. package/crates/tish_compile_js/src/tests_jsx.rs +89 -19
  22. package/crates/tish_compiler_wasm/src/lib.rs +2 -3
  23. package/crates/tish_core/Cargo.toml +4 -0
  24. package/crates/tish_core/src/console_style.rs +7 -1
  25. package/crates/tish_core/src/json.rs +1 -2
  26. package/crates/tish_core/src/macros.rs +2 -3
  27. package/crates/tish_core/src/value.rs +10 -5
  28. package/crates/tish_eval/Cargo.toml +2 -0
  29. package/crates/tish_eval/src/eval.rs +149 -72
  30. package/crates/tish_eval/src/http.rs +3 -4
  31. package/crates/tish_eval/src/regex.rs +3 -2
  32. package/crates/tish_eval/src/value.rs +11 -13
  33. package/crates/tish_eval/src/value_convert.rs +4 -8
  34. package/crates/tish_fmt/src/lib.rs +49 -10
  35. package/crates/tish_jsx_web/Cargo.toml +1 -1
  36. package/crates/tish_jsx_web/README.md +3 -16
  37. package/crates/tish_jsx_web/src/lib.rs +2 -157
  38. package/crates/tish_lexer/src/token.rs +2 -0
  39. package/crates/tish_lint/src/lib.rs +9 -0
  40. package/crates/tish_lsp/README.md +1 -1
  41. package/crates/tish_native/src/build.rs +16 -2
  42. package/crates/tish_opt/src/lib.rs +15 -0
  43. package/crates/tish_parser/src/lib.rs +101 -1
  44. package/crates/tish_parser/src/parser.rs +161 -50
  45. package/crates/tish_runtime/src/http.rs +4 -5
  46. package/crates/tish_runtime/src/http_fetch.rs +9 -10
  47. package/crates/tish_runtime/src/lib.rs +9 -2
  48. package/crates/tish_runtime/src/promise.rs +2 -3
  49. package/crates/tish_runtime/src/promise_io.rs +2 -3
  50. package/crates/tish_runtime/src/ws.rs +7 -7
  51. package/crates/tish_ui/Cargo.toml +17 -0
  52. package/crates/tish_ui/src/jsx.rs +390 -0
  53. package/crates/tish_ui/src/lib.rs +16 -0
  54. package/crates/tish_ui/src/runtime/hooks.rs +122 -0
  55. package/crates/tish_ui/src/runtime/mod.rs +173 -0
  56. package/crates/tish_vm/src/vm.rs +121 -27
  57. package/justfile +3 -3
  58. package/package.json +1 -1
  59. package/platform/darwin-arm64/tish +0 -0
  60. package/platform/darwin-x64/tish +0 -0
  61. package/platform/linux-arm64/tish +0 -0
  62. package/platform/linux-x64/tish +0 -0
  63. package/platform/win32-x64/tish.exe +0 -0
  64. package/crates/tish_compile_js/src/js_intrinsics.rs +0 -82
  65. package/crates/tish_jsx_web/vendor/Lattish.tish +0 -362
@@ -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 {
@@ -468,6 +481,8 @@ struct Codegen {
468
481
  usage_analyzer: Option<UsageAnalyzer>,
469
482
  /// Type context for tracking variable types (for static typing)
470
483
  type_context: TypeContext,
484
+ /// Program uses JSX; emit `tishlang_ui` imports and `h` / `Fragment` globals.
485
+ program_has_jsx: bool,
471
486
  }
472
487
 
473
488
  impl Codegen {
@@ -493,6 +508,7 @@ impl Codegen {
493
508
  refcell_wrapped_vars: std::collections::HashSet::new(),
494
509
  usage_analyzer: None,
495
510
  type_context: TypeContext::new(),
511
+ program_has_jsx: false,
496
512
  }
497
513
  }
498
514
 
@@ -503,7 +519,12 @@ impl Codegen {
503
519
  return self.builtin_native_module_rust_init(spec, export_name);
504
520
  }
505
521
  self.native_module_map.get(spec).map(|(crate_name, export_fn)| {
506
- format!("{}::{}()", crate_name, export_fn)
522
+ // Native modules return a namespace object (like an ES module).
523
+ // Named imports extract the field from that namespace: `import { foo } from "pkg"` → `ns.foo`.
524
+ format!(
525
+ "{{ let _ns = {}::{}(); match _ns {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }} }}",
526
+ crate_name, export_fn, export_name
527
+ )
507
528
  })
508
529
  }
509
530
 
@@ -536,7 +557,7 @@ impl Codegen {
536
557
  "exec" => Some("Value::Function(Rc::new(|args: &[Value]| tish_process_exec(args)))"),
537
558
  "argv" => Some("Value::Array(Rc::new(RefCell::new(std::env::args().map(|s| Value::String(s.into())).collect())))"),
538
559
  "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))) }"),
560
+ "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
561
  _ => None,
541
562
  },
542
563
  "tish:ws" if self.has_feature("ws") => match export_name {
@@ -634,6 +655,7 @@ impl Codegen {
634
655
  | Expr::Array { .. }
635
656
  | Expr::Object { .. }
636
657
  | Expr::Call { .. }
658
+ | Expr::New { .. }
637
659
  | Expr::Await { .. }
638
660
  | Expr::ArrowFunction { .. }
639
661
  | Expr::Binary { .. }
@@ -727,12 +749,17 @@ impl Codegen {
727
749
  use tishlang_ast::ArrowBody;
728
750
 
729
751
  if let Expr::ArrowFunction { params, body, .. } = expr {
730
- // Must have exactly 2 params
731
752
  if params.len() != 2 {
732
753
  return None;
733
754
  }
734
- let param_a = params[0].name.as_ref();
735
- let param_b = params[1].name.as_ref();
755
+ let (param_a, param_b) = match (&params[0], &params[1]) {
756
+ (FunParam::Simple(a), FunParam::Simple(b))
757
+ if a.default.is_none() && b.default.is_none() =>
758
+ {
759
+ (a.name.as_ref(), b.name.as_ref())
760
+ }
761
+ _ => return None,
762
+ };
736
763
 
737
764
  // Body must be a single expression that's a subtraction
738
765
  let body_expr = match body {
@@ -763,12 +790,15 @@ impl Codegen {
763
790
 
764
791
  fn emit_program(&mut self, program: &Program) -> Result<(), CompileError> {
765
792
  self.is_async = program_uses_async(program);
793
+ self.program_has_jsx = tishlang_ui::jsx::program_contains_jsx(program);
766
794
  self.write("#![allow(unused, non_snake_case)]\n\n");
767
795
  self.write("use std::cell::RefCell;\n");
768
- self.write("use std::collections::HashMap;\n");
769
796
  self.write("use std::rc::Rc;\n");
770
797
  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");
798
+ 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");
799
+ if self.program_has_jsx {
800
+ self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
801
+ }
772
802
  if self.has_feature("process") {
773
803
  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
804
  }
@@ -818,7 +848,7 @@ impl Codegen {
818
848
  self.indent += 1;
819
849
 
820
850
  // Initialize builtins
821
- self.writeln("let mut console = Value::Object(Rc::new(RefCell::new(HashMap::from([");
851
+ self.writeln("let mut console = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
822
852
  self.indent += 1;
823
853
  self.writeln("(Arc::from(\"debug\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_debug(args); Value::Null }))),");
824
854
  self.writeln("(Arc::from(\"info\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_info(args); Value::Null }))),");
@@ -836,7 +866,7 @@ impl Codegen {
836
866
  self.writeln("let isNaN = Value::Function(Rc::new(|args: &[Value]| tish_is_nan(args)));");
837
867
  self.writeln("let Infinity = Value::Number(f64::INFINITY);");
838
868
  self.writeln("let NaN = Value::Number(f64::NAN);");
839
- self.writeln("let Math = Value::Object(Rc::new(RefCell::new(HashMap::from([");
869
+ self.writeln("let Math = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
840
870
  self.indent += 1;
841
871
  self.writeln("(Arc::from(\"abs\"), Value::Function(Rc::new(|args: &[Value]| tish_math_abs(args)))),");
842
872
  self.writeln("(Arc::from(\"sqrt\"), Value::Function(Rc::new(|args: &[Value]| tish_math_sqrt(args)))),");
@@ -858,32 +888,32 @@ impl Codegen {
858
888
  self.writeln("(Arc::from(\"E\"), Value::Number(std::f64::consts::E)),");
859
889
  self.indent -= 1;
860
890
  self.writeln("]))));");
861
- self.writeln("let JSON = Value::Object(Rc::new(RefCell::new(HashMap::from([");
891
+ self.writeln("let JSON = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
862
892
  self.indent += 1;
863
893
  self.writeln("(Arc::from(\"parse\"), Value::Function(Rc::new(|args: &[Value]| tish_json_parse(args)))),");
864
894
  self.writeln("(Arc::from(\"stringify\"), Value::Function(Rc::new(|args: &[Value]| tish_json_stringify(args)))),");
865
895
  self.indent -= 1;
866
896
  self.writeln("]))));");
867
897
 
868
- self.writeln("let Array = Value::Object(Rc::new(RefCell::new(HashMap::from([");
898
+ self.writeln("let Array = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
869
899
  self.indent += 1;
870
900
  self.writeln("(Arc::from(\"isArray\"), Value::Function(Rc::new(|args: &[Value]| tish_array_is_array(args)))),");
871
901
  self.indent -= 1;
872
902
  self.writeln("]))));");
873
903
 
874
- self.writeln("let String = Value::Object(Rc::new(RefCell::new(HashMap::from([");
904
+ self.writeln("let String = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
875
905
  self.indent += 1;
876
906
  self.writeln("(Arc::from(\"fromCharCode\"), Value::Function(Rc::new(|args: &[Value]| tish_string_from_char_code(args)))),");
877
907
  self.indent -= 1;
878
908
  self.writeln("]))));");
879
909
 
880
- self.writeln("let Date = Value::Object(Rc::new(RefCell::new(HashMap::from([");
910
+ self.writeln("let Date = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
881
911
  self.indent += 1;
882
912
  self.writeln("(Arc::from(\"now\"), Value::Function(Rc::new(|args: &[Value]| tish_date_now(args)))),");
883
913
  self.indent -= 1;
884
914
  self.writeln("]))));");
885
915
 
886
- self.writeln("let Object = Value::Object(Rc::new(RefCell::new(HashMap::from([");
916
+ self.writeln("let Object = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
887
917
  self.indent += 1;
888
918
  self.writeln("(Arc::from(\"assign\"), Value::Function(Rc::new(|args: &[Value]| tish_object_assign(args)))),");
889
919
  self.writeln("(Arc::from(\"keys\"), Value::Function(Rc::new(|args: &[Value]| tish_object_keys(args)))),");
@@ -893,16 +923,19 @@ impl Codegen {
893
923
  self.indent -= 1;
894
924
  self.writeln("]))));");
895
925
 
926
+ self.writeln("let Uint8Array = tish_uint8_array_constructor();");
927
+ self.writeln("let AudioContext = tish_audio_context_constructor();");
928
+
896
929
  if self.has_feature("process") {
897
930
  self.writeln("let process = Value::Object(Rc::new(RefCell::new({");
898
931
  self.indent += 1;
899
- self.writeln("let mut p = HashMap::new();");
932
+ self.writeln("let mut p = ObjectMap::default();");
900
933
  self.writeln("p.insert(Arc::from(\"exit\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exit(args))));");
901
934
  self.writeln("p.insert(Arc::from(\"cwd\"), Value::Function(Rc::new(|args: &[Value]| tish_process_cwd(args))));");
902
935
  self.writeln("p.insert(Arc::from(\"exec\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exec(args))));");
903
936
  self.writeln("let argv: Vec<Value> = std::env::args().map(|s| Value::String(s.into())).collect();");
904
937
  self.writeln("p.insert(Arc::from(\"argv\"), Value::Array(Rc::new(RefCell::new(argv))));");
905
- self.writeln("let mut env_obj = HashMap::new();");
938
+ self.writeln("let mut env_obj = ObjectMap::default();");
906
939
  self.writeln("for (key, value) in std::env::vars() {");
907
940
  self.indent += 1;
908
941
  self.writeln("env_obj.insert(Arc::from(key.as_str()), Value::String(value.into()));");
@@ -952,6 +985,15 @@ impl Codegen {
952
985
  self.writeln("let RegExp = Value::Function(Rc::new(|args: &[Value]| regexp_new(args)));");
953
986
  }
954
987
 
988
+ if self.program_has_jsx {
989
+ self.writeln("install_thread_local_host(Box::new(HeadlessHost::default()));");
990
+ self.writeln("let Fragment = fragment_value();");
991
+ self.writeln("let h = Value::Function(Rc::new(|args: &[Value]| ui_h(args)));");
992
+ self.writeln("let text = Value::Function(Rc::new(|args: &[Value]| ui_text(args)));");
993
+ self.writeln("let useState = Value::Function(Rc::new(|args: &[Value]| native_use_state(args)));");
994
+ self.writeln("let createRoot = Value::Function(Rc::new(|args: &[Value]| native_create_root(args)));");
995
+ }
996
+
955
997
  // Polars, Egui etc. are emitted via VarDecl from import { X } from 'tish:...'
956
998
 
957
999
  // Pre-scan for top-level function declarations and create cells (for mutual recursion)
@@ -1078,11 +1120,8 @@ impl Codegen {
1078
1120
  let expr = self.emit_expr(init)?;
1079
1121
  let mutability = if *mutable { "let mut" } else { "let" };
1080
1122
  let clone_suffix = if Self::needs_clone(init) { ".clone()" } else { "" };
1081
- self.writeln(&format!("{{ let _destruct_val = ({}){};", expr, clone_suffix));
1082
- self.indent += 1;
1123
+ self.writeln(&format!("let _destruct_val = ({}){};", expr, clone_suffix));
1083
1124
  self.emit_destruct_bindings(pattern, "_destruct_val", mutability, *span)?;
1084
- self.indent -= 1;
1085
- self.writeln("}");
1086
1125
  }
1087
1126
  Statement::ExprStmt { expr, .. } => {
1088
1127
  let e = self.emit_expr(expr)?;
@@ -1326,7 +1365,7 @@ impl Codegen {
1326
1365
  self.emit_statement(finally_stmt)?;
1327
1366
  }
1328
1367
  }
1329
- Statement::FunDecl { name, params, rest_param, body, .. } => {
1368
+ Statement::FunDecl { name, params, rest_param, body, span, .. } => {
1330
1369
  // Use Rc<RefCell<>> pattern to allow recursive function calls
1331
1370
  // The function can reference itself through the cell
1332
1371
  let name_raw = name.as_ref();
@@ -1343,7 +1382,11 @@ impl Codegen {
1343
1382
  // Analyze body to find which identifiers are actually referenced
1344
1383
  let mut referenced = HashSet::new();
1345
1384
  Self::collect_stmt_idents(body, &mut referenced);
1346
- let param_names: HashSet<String> = params.iter().map(|p| p.name.to_string()).collect();
1385
+ let param_names: HashSet<String> = params
1386
+ .iter()
1387
+ .flat_map(|p| p.bound_names())
1388
+ .map(|n| n.to_string())
1389
+ .collect();
1347
1390
 
1348
1391
  // Collect all outer parameters that need to be captured (only those referenced)
1349
1392
  let outer_params: Vec<String> = self.outer_params_stack
@@ -1418,7 +1461,7 @@ impl Codegen {
1418
1461
  self.writeln(&format!("let {} = {}.clone();", param_escaped, param_escaped));
1419
1462
  }
1420
1463
  // 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"] {
1464
+ for builtin in &["Boolean", "console", "Math", "JSON", "Date", "Uint8Array", "AudioContext", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"] {
1422
1465
  if referenced.contains(*builtin) {
1423
1466
  self.writeln(&format!("let {} = {}.clone();", builtin, builtin));
1424
1467
  }
@@ -1445,13 +1488,30 @@ impl Codegen {
1445
1488
  self.writeln(&format!("let {} = (*{}_ref.borrow()).clone();", sibling_escaped, sibling_escaped));
1446
1489
  }
1447
1490
  // 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();
1491
+ let current_param_names: Vec<String> = params
1492
+ .iter()
1493
+ .flat_map(|p| p.bound_names())
1494
+ .map(|n| n.to_string())
1495
+ .collect();
1496
+ let formal_span = *span;
1449
1497
  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
- ));
1498
+ match p {
1499
+ FunParam::Simple(tp) => {
1500
+ self.writeln(&format!(
1501
+ "let mut {} = args.get({}).cloned().unwrap_or(Value::Null);",
1502
+ Self::escape_ident(tp.name.as_ref()),
1503
+ i
1504
+ ));
1505
+ }
1506
+ FunParam::Destructure { pattern, .. } => {
1507
+ let tmp = format!("_formal_{}", i);
1508
+ self.writeln(&format!(
1509
+ "let {} = args.get({}).cloned().unwrap_or(Value::Null);",
1510
+ tmp, i
1511
+ ));
1512
+ self.emit_destruct_bindings(pattern, &tmp, "let mut", formal_span)?;
1513
+ }
1514
+ }
1455
1515
  }
1456
1516
  if let Some(rest) = rest_param {
1457
1517
  self.writeln(&format!(
@@ -1562,58 +1622,71 @@ impl Codegen {
1562
1622
  }
1563
1623
 
1564
1624
  fn emit_destruct_bindings(&mut self, pattern: &DestructPattern, value_expr: &str, mutability: &str, span: Span) -> Result<(), CompileError> {
1625
+ // Flat `let` bindings so names stay in scope for the rest of the function (e.g. JSX).
1565
1626
  match pattern {
1566
1627
  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
1628
  for (i, elem) in elements.iter().enumerate() {
1571
1629
  if let Some(el) = elem {
1572
1630
  match el {
1573
1631
  DestructElement::Ident(name) => {
1574
- self.writeln(&format!("{} {} = _arr_borrow.get({}).cloned().unwrap_or(Value::Null);",
1575
- mutability, Self::escape_ident(name.as_ref()), i));
1632
+ self.writeln(&format!(
1633
+ "{} {} = match &({}) {{ Value::Array(ref _a) => _a.borrow().get({}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
1634
+ mutability,
1635
+ Self::escape_ident(name.as_ref()),
1636
+ value_expr,
1637
+ i
1638
+ ));
1576
1639
  }
1577
1640
  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));
1641
+ let nested_var = format!("_nested_arr_{}", i);
1642
+ self.writeln(&format!(
1643
+ "let {} = match &({}) {{ Value::Array(ref _a) => _a.borrow().get({}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
1644
+ nested_var, value_expr, i
1645
+ ));
1581
1646
  self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
1582
1647
  }
1583
1648
  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));
1649
+ self.writeln(&format!(
1650
+ "{} {} = 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()))) }};",
1651
+ mutability,
1652
+ Self::escape_ident(name.as_ref()),
1653
+ value_expr,
1654
+ i
1655
+ ));
1586
1656
  }
1587
1657
  }
1588
1658
  }
1589
1659
  }
1590
- self.indent -= 1;
1591
- self.writeln("}");
1592
1660
  }
1593
1661
  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
1662
  for prop in props {
1598
1663
  let key = prop.key.as_ref();
1599
1664
  match &prop.value {
1600
1665
  DestructElement::Ident(name) => {
1601
- self.writeln(&format!("{} {} = _obj_borrow.get({:?}).cloned().unwrap_or(Value::Null);",
1602
- mutability, Self::escape_ident(name.as_ref()), key));
1666
+ self.writeln(&format!(
1667
+ "{} {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
1668
+ mutability,
1669
+ Self::escape_ident(name.as_ref()),
1670
+ value_expr,
1671
+ key
1672
+ ));
1603
1673
  }
1604
1674
  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));
1675
+ let nested_var = format!("_nested_obj_{}", key);
1676
+ self.writeln(&format!(
1677
+ "let {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
1678
+ nested_var, value_expr, key
1679
+ ));
1608
1680
  self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
1609
1681
  }
1610
1682
  DestructElement::Rest(_) => {
1611
- return Err(CompileError::new("Rest in object destructuring not supported", Some(span)));
1683
+ return Err(CompileError::new(
1684
+ "Rest in object destructuring not supported",
1685
+ Some(span),
1686
+ ));
1612
1687
  }
1613
1688
  }
1614
1689
  }
1615
- self.indent -= 1;
1616
- self.writeln("}");
1617
1690
  }
1618
1691
  }
1619
1692
  Ok(())
@@ -2164,7 +2237,7 @@ impl Codegen {
2164
2237
  }
2165
2238
  }
2166
2239
  }
2167
- format!("{{ let mut _obj: HashMap<Arc<str>, Value> = HashMap::new(); {} Value::Object(Rc::new(RefCell::new(_obj))) }}", parts.join(" "))
2240
+ format!("{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::Object(Rc::new(RefCell::new(_obj))) }}", parts.join(" "))
2168
2241
  } else {
2169
2242
  let mut parts = Vec::new();
2170
2243
  for prop in props {
@@ -2178,7 +2251,7 @@ impl Codegen {
2178
2251
  }
2179
2252
  }
2180
2253
  format!(
2181
- "Value::Object(Rc::new(RefCell::new(HashMap::from([{}]))))",
2254
+ "Value::Object(Rc::new(RefCell::new(ObjectMap::from([{}]))))",
2182
2255
  parts.join(", ")
2183
2256
  )
2184
2257
  }
@@ -2338,8 +2411,8 @@ impl Codegen {
2338
2411
  val
2339
2412
  )
2340
2413
  }
2341
- Expr::ArrowFunction { params, body, .. } => {
2342
- self.emit_arrow_function(params, body)?
2414
+ Expr::ArrowFunction { params, body, span, .. } => {
2415
+ self.emit_arrow_function(params, body, *span)?
2343
2416
  }
2344
2417
  Expr::TemplateLiteral { quasis, exprs, .. } => {
2345
2418
  // Build the template string
@@ -2356,7 +2429,33 @@ impl Codegen {
2356
2429
  format!("Value::String([{}].concat().into())", parts.join(", "))
2357
2430
  }
2358
2431
  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 });
2432
+ tishlang_ui::jsx::emit_jsx_rust(expr, &mut |e| {
2433
+ self.emit_expr(e).map_err(|ce| ce.message)
2434
+ })
2435
+ .map_err(|m| CompileError::new(m, None))?
2436
+ }
2437
+ Expr::New { callee, args, .. } => {
2438
+ let callee_expr = self.emit_expr(callee)?;
2439
+ let has_spread = args.iter().any(|a| matches!(a, CallArg::Spread(_)));
2440
+ if has_spread {
2441
+ let args_code = self.emit_call_args(args)?;
2442
+ return Ok(format!(
2443
+ "{{ let _callee = ({}).clone(); let _spread_args = {}; tish_construct(&_callee, &_spread_args[..]) }}",
2444
+ callee_expr, args_code
2445
+ ));
2446
+ }
2447
+ let arg_exprs: Result<Vec<_>, _> =
2448
+ args.iter().map(|a| self.emit_call_arg(a)).collect();
2449
+ let arg_exprs = arg_exprs?;
2450
+ let args_vec = arg_exprs
2451
+ .iter()
2452
+ .map(|a| format!("{}.clone()", a))
2453
+ .collect::<Vec<_>>()
2454
+ .join(", ");
2455
+ format!(
2456
+ "tish_construct(&({}).clone(), &[{}])",
2457
+ callee_expr, args_vec
2458
+ )
2360
2459
  }
2361
2460
  Expr::NativeModuleLoad { spec, export_name, .. } => {
2362
2461
  self.native_module_rust_init(spec.as_ref(), export_name.as_ref())
@@ -2410,6 +2509,14 @@ impl Codegen {
2410
2509
  }
2411
2510
  }
2412
2511
  }
2512
+ Expr::New { callee, args, .. } => {
2513
+ Self::collect_expr_idents(callee, idents);
2514
+ for arg in args {
2515
+ match arg {
2516
+ CallArg::Expr(e) | CallArg::Spread(e) => Self::collect_expr_idents(e, idents),
2517
+ }
2518
+ }
2519
+ }
2413
2520
  Expr::Member { object, prop, .. } => {
2414
2521
  Self::collect_expr_idents(object, idents);
2415
2522
  if let MemberProp::Expr(e) = prop { Self::collect_expr_idents(e, idents); }
@@ -2613,6 +2720,16 @@ impl Codegen {
2613
2720
  }
2614
2721
  }
2615
2722
  }
2723
+ Expr::New { callee, args, .. } => {
2724
+ Self::collect_assigned_idents_in_expr(callee, names);
2725
+ for arg in args {
2726
+ match arg {
2727
+ CallArg::Expr(e) | CallArg::Spread(e) => {
2728
+ Self::collect_assigned_idents_in_expr(e, names);
2729
+ }
2730
+ }
2731
+ }
2732
+ }
2616
2733
  Expr::Member { object, prop, .. } => {
2617
2734
  Self::collect_assigned_idents_in_expr(object, names);
2618
2735
  if let MemberProp::Expr(e) = prop {
@@ -2725,12 +2842,16 @@ impl Codegen {
2725
2842
  /// Collect variable names that are both captured and mutated by a closure body.
2726
2843
  /// block_vars: vars declared in the enclosing block (candidates for mutation).
2727
2844
  fn collect_mutated_captures_from_closure(
2728
- params: &[tishlang_ast::TypedParam],
2845
+ params: &[FunParam],
2729
2846
  body: &Statement,
2730
2847
  block_vars: &HashSet<String>,
2731
2848
  result: &mut HashSet<String>,
2732
2849
  ) {
2733
- let param_names: HashSet<String> = params.iter().map(|p| p.name.to_string()).collect();
2850
+ let param_names: HashSet<String> = params
2851
+ .iter()
2852
+ .flat_map(|p| p.bound_names())
2853
+ .map(|n| n.to_string())
2854
+ .collect();
2734
2855
  let mut local_var_names = HashSet::new();
2735
2856
  Self::collect_local_var_names(body, &mut local_var_names);
2736
2857
  let mut referenced = HashSet::new();
@@ -2754,12 +2875,16 @@ impl Codegen {
2754
2875
  }
2755
2876
 
2756
2877
  fn collect_mutated_captures_from_arrow(
2757
- params: &[tishlang_ast::TypedParam],
2878
+ params: &[FunParam],
2758
2879
  body: &ArrowBody,
2759
2880
  block_vars: &HashSet<String>,
2760
2881
  result: &mut HashSet<String>,
2761
2882
  ) {
2762
- let param_names: HashSet<String> = params.iter().map(|p| p.name.to_string()).collect();
2883
+ let param_names: HashSet<String> = params
2884
+ .iter()
2885
+ .flat_map(|p| p.bound_names())
2886
+ .map(|n| n.to_string())
2887
+ .collect();
2763
2888
  let mut local_var_names = HashSet::new();
2764
2889
  match body {
2765
2890
  ArrowBody::Expr(_) => {}
@@ -2808,6 +2933,16 @@ impl Codegen {
2808
2933
  }
2809
2934
  }
2810
2935
  }
2936
+ Expr::New { callee, args, .. } => {
2937
+ Self::collect_mutated_captures_from_expr(callee, block_vars, result);
2938
+ for arg in args {
2939
+ match arg {
2940
+ CallArg::Expr(e) | CallArg::Spread(e) => {
2941
+ Self::collect_mutated_captures_from_expr(e, block_vars, result);
2942
+ }
2943
+ }
2944
+ }
2945
+ }
2811
2946
  Expr::Member { object, prop, .. } => {
2812
2947
  Self::collect_mutated_captures_from_expr(object, block_vars, result);
2813
2948
  if let MemberProp::Expr(e) = prop {
@@ -3055,8 +3190,9 @@ impl Codegen {
3055
3190
 
3056
3191
  fn emit_arrow_function(
3057
3192
  &mut self,
3058
- params: &[tishlang_ast::TypedParam],
3193
+ params: &[FunParam],
3059
3194
  body: &tishlang_ast::ArrowBody,
3195
+ span: Span,
3060
3196
  ) -> Result<String, CompileError> {
3061
3197
  // Build the arrow function as a Value::Function closure
3062
3198
  let mut code = String::new();
@@ -3065,7 +3201,11 @@ impl Codegen {
3065
3201
  // Find which identifiers are actually referenced in the body
3066
3202
  let referenced = Self::collect_referenced_idents(body);
3067
3203
  // 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();
3204
+ let param_names: HashSet<String> = params
3205
+ .iter()
3206
+ .flat_map(|p| p.bound_names())
3207
+ .map(|n| n.to_string())
3208
+ .collect();
3069
3209
 
3070
3210
  // Exclude variables declared inside the arrow body (locals)
3071
3211
  let mut local_var_names = HashSet::new();
@@ -3120,7 +3260,7 @@ impl Codegen {
3120
3260
  }
3121
3261
  }
3122
3262
  // 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"] {
3263
+ for builtin in &["console", "Math", "JSON", "Date", "Uint8Array", "AudioContext", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"] {
3124
3264
  if referenced.contains(*builtin) {
3125
3265
  code.push_str(&format!(" let {} = {}.clone();\n", builtin, builtin));
3126
3266
  }
@@ -3164,13 +3304,35 @@ impl Codegen {
3164
3304
  }
3165
3305
 
3166
3306
  // Extract parameters from args
3167
- let current_param_names: Vec<String> = params.iter().map(|p| p.name.to_string()).collect();
3307
+ let current_param_names: Vec<String> = params
3308
+ .iter()
3309
+ .flat_map(|p| p.bound_names())
3310
+ .map(|n| n.to_string())
3311
+ .collect();
3168
3312
  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
- ));
3313
+ match p {
3314
+ FunParam::Simple(tp) => {
3315
+ code.push_str(&format!(
3316
+ " let mut {} = args.get({}).cloned().unwrap_or(Value::Null);\n",
3317
+ Self::escape_ident(tp.name.as_ref()),
3318
+ i
3319
+ ));
3320
+ }
3321
+ FunParam::Destructure { pattern, .. } => {
3322
+ let tmp = format!("_formal_{}", i);
3323
+ code.push_str(&format!(
3324
+ " let {} = args.get({}).cloned().unwrap_or(Value::Null);\n",
3325
+ tmp, i
3326
+ ));
3327
+ let saved = std::mem::take(&mut self.output);
3328
+ let saved_indent = self.indent;
3329
+ self.indent = 8;
3330
+ self.emit_destruct_bindings(pattern, &tmp, "let mut", span)?;
3331
+ let frag = std::mem::replace(&mut self.output, saved);
3332
+ self.indent = saved_indent;
3333
+ code.push_str(&frag);
3334
+ }
3335
+ }
3174
3336
  }
3175
3337
 
3176
3338
  // Push current params for potential nested arrows
@@ -53,6 +53,41 @@ for (let i = 0; i < 5; i = i + 1) {
53
53
  );
54
54
  }
55
55
 
56
+ #[test]
57
+ fn new_expression_lowers_to_construct_on_native() {
58
+ let src = "fn f() { return new Uint8Array(4) }";
59
+ let program = parse(src).unwrap();
60
+ let rust = compile(&program).unwrap();
61
+ assert!(
62
+ rust.contains("tish_construct"),
63
+ "expected new to lower to tish_construct, got snippet missing it"
64
+ );
65
+ }
66
+
67
+ /// User-defined constructor name: `new ClassName(...)` must compile natively (host `construct`)
68
+ /// and is the same surface syntax as the JS target (`new` in emitted JavaScript).
69
+ #[test]
70
+ fn new_class_name_compiles_native_via_tish_construct() {
71
+ let src = r#"
72
+ fn ClassName(x) {
73
+ return x
74
+ }
75
+ fn factory() {
76
+ return new ClassName(42)
77
+ }
78
+ "#;
79
+ let program = parse(src).unwrap();
80
+ let rust = compile(&program).unwrap();
81
+ assert!(
82
+ rust.contains("tish_construct"),
83
+ "expected new ClassName to lower to tish_construct"
84
+ );
85
+ assert!(
86
+ rust.contains("ClassName"),
87
+ "expected emitted Rust to reference ClassName callable"
88
+ );
89
+ }
90
+
56
91
  #[test]
57
92
  fn loop_var_decl_clone_via_project_full() {
58
93
  let manifest = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));