@tishlang/tish 2.10.1 → 2.16.13

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 (75) hide show
  1. package/Cargo.toml +3 -0
  2. package/bin/tish +0 -0
  3. package/crates/tish/Cargo.toml +1 -1
  4. package/crates/tish/src/cli_help.rs +16 -0
  5. package/crates/tish/src/main.rs +24 -4
  6. package/crates/tish/tests/integration_test.rs +149 -0
  7. package/crates/tish_ast/src/ast.rs +10 -0
  8. package/crates/tish_builtins/src/array.rs +529 -56
  9. package/crates/tish_builtins/src/collections.rs +114 -40
  10. package/crates/tish_builtins/src/string.rs +95 -8
  11. package/crates/tish_bytecode/src/chunk.rs +7 -0
  12. package/crates/tish_bytecode/src/compiler.rs +560 -64
  13. package/crates/tish_bytecode/src/lib.rs +1 -1
  14. package/crates/tish_bytecode/src/opcode.rs +154 -2
  15. package/crates/tish_bytecode/src/serialize.rs +2 -0
  16. package/crates/tish_bytecode/tests/append_local_string_builder.rs +63 -0
  17. package/crates/tish_bytecode/tests/math_unary_intrinsic.rs +47 -0
  18. package/crates/tish_compile/src/codegen.rs +17736 -5269
  19. package/crates/tish_compile/src/infer.rs +1707 -190
  20. package/crates/tish_compile/src/lib.rs +29 -4
  21. package/crates/tish_compile/src/resolve.rs +66 -5
  22. package/crates/tish_compile/src/types.rs +224 -28
  23. package/crates/tish_compile/tests/dump_codegen.rs +21 -0
  24. package/crates/tish_compile/tests/perf_codegen_169_173.rs +8 -17
  25. package/crates/tish_compile/tests/perf_codegen_173_part3.rs +61 -0
  26. package/crates/tish_compile/tests/perf_codegen_174.rs +160 -0
  27. package/crates/tish_compile/tests/perf_codegen_175.rs +190 -0
  28. package/crates/tish_compile/tests/perf_codegen_176.rs +60 -0
  29. package/crates/tish_compile/tests/perf_codegen_177.rs +167 -0
  30. package/crates/tish_compile/tests/perf_codegen_178.rs +114 -0
  31. package/crates/tish_compile/tests/perf_codegen_178_rec.rs +124 -0
  32. package/crates/tish_compile/tests/perf_codegen_181.rs +36 -0
  33. package/crates/tish_compile/tests/perf_codegen_320.rs +211 -0
  34. package/crates/tish_compile/tests/perf_codegen_module_const_forof.rs +40 -0
  35. package/crates/tish_compile_js/src/codegen.rs +91 -4
  36. package/crates/tish_compile_js/src/lib.rs +5 -2
  37. package/crates/tish_compile_js/src/tests_jsx.rs +175 -7
  38. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +100 -2
  39. package/crates/tish_core/Cargo.toml +4 -0
  40. package/crates/tish_core/src/json.rs +104 -5
  41. package/crates/tish_core/src/lib.rs +174 -0
  42. package/crates/tish_core/src/shape.rs +4 -2
  43. package/crates/tish_core/src/value.rs +565 -35
  44. package/crates/tish_core/src/vmref.rs +14 -0
  45. package/crates/tish_eval/src/eval.rs +675 -76
  46. package/crates/tish_eval/src/natives.rs +19 -0
  47. package/crates/tish_eval/src/value.rs +76 -21
  48. package/crates/tish_ffi/src/lib.rs +11 -1
  49. package/crates/tish_ffi/tests/double_free.rs +35 -0
  50. package/crates/tish_fmt/src/lib.rs +75 -1
  51. package/crates/tish_lexer/src/lib.rs +76 -0
  52. package/crates/tish_lexer/src/token.rs +4 -0
  53. package/crates/tish_lint/src/lib.rs +126 -0
  54. package/crates/tish_lsp/README.md +2 -1
  55. package/crates/tish_lsp/src/main.rs +378 -28
  56. package/crates/tish_native/src/build.rs +41 -0
  57. package/crates/tish_parser/Cargo.toml +4 -0
  58. package/crates/tish_parser/src/lib.rs +27 -0
  59. package/crates/tish_parser/src/parser.rs +302 -20
  60. package/crates/tish_resolve/src/lib.rs +9 -0
  61. package/crates/tish_runtime/Cargo.toml +5 -0
  62. package/crates/tish_runtime/src/http.rs +28 -10
  63. package/crates/tish_runtime/src/http_fetch.rs +150 -1
  64. package/crates/tish_runtime/src/http_prefork.rs +72 -11
  65. package/crates/tish_runtime/src/lib.rs +523 -42
  66. package/crates/tish_runtime/src/timers.rs +49 -2
  67. package/crates/tish_ui/src/jsx.rs +253 -0
  68. package/crates/tish_vm/src/jit.rs +2514 -117
  69. package/crates/tish_vm/src/vm.rs +1227 -182
  70. package/package.json +1 -1
  71. package/platform/darwin-arm64/tish +0 -0
  72. package/platform/darwin-x64/tish +0 -0
  73. package/platform/linux-arm64/tish +0 -0
  74. package/platform/linux-x64/tish +0 -0
  75. package/platform/win32-x64/tish.exe +0 -0
package/Cargo.toml CHANGED
@@ -34,6 +34,9 @@ members = [
34
34
  "crates/tish_resolve",
35
35
  "crates/tishlang_cargo_bindgen",
36
36
  ]
37
+ # Generated native-build projects (`tish build`) land under `target/` when TMPDIR points there;
38
+ # never enroll anything beneath `target/` as a workspace member.
39
+ exclude = ["target"]
37
40
  resolver = "2"
38
41
 
39
42
  [workspace.package]
package/bin/tish CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tishlang"
3
- version = "2.10.1"
3
+ version = "2.16.13"
4
4
  edition = "2021"
5
5
  description = "Tish CLI - run, REPL, compile to native"
6
6
  license-file = { workspace = true }
@@ -563,6 +563,14 @@ pub(crate) struct BuildArgs {
563
563
  /// For `--target js`: `bundle` (default) merges all modules into one file; `esm` emits one `.js` per `.tish` module with real `import`/`export` (so `-o` is a directory) for Vite/Rollup tree-shaking.
564
564
  #[arg(long, default_value = "bundle", value_name = "FORMAT", help_heading = "Options")]
565
565
  pub format: String,
566
+ /// For `--target js --format esm`: package the JSX runtime (`h`/`Fragment`) is auto-imported from when a module uses JSX without importing them (default `lattish`).
567
+ #[arg(
568
+ long = "jsx-import-source",
569
+ default_value = "lattish",
570
+ value_name = "PKG",
571
+ help_heading = "Options"
572
+ )]
573
+ pub jsx_import_source: String,
566
574
  /// Run the gradual type checker: `warn` prints `line:col` type diagnostics; `error` also fails the build on them. (Equivalent to setting `TISH_CHECK`.)
567
575
  #[arg(long, value_name = "MODE", help_heading = "Options")]
568
576
  pub check: Option<String>,
@@ -594,6 +602,14 @@ pub(crate) struct CompileModuleArgs {
594
602
  /// Disable AST optimizations (forced on when a source map is emitted).
595
603
  #[arg(long, help_heading = "Options")]
596
604
  pub no_optimize: bool,
605
+ /// Package the JSX runtime (`h`/`Fragment`) is auto-imported from when a module uses JSX without importing them (default `lattish`).
606
+ #[arg(
607
+ long = "jsx-import-source",
608
+ default_value = "lattish",
609
+ value_name = "PKG",
610
+ help_heading = "Options"
611
+ )]
612
+ pub jsx_import_source: String,
597
613
  /// Entry `.tish` file to compile (only this file is read; dependencies are left to the bundler).
598
614
  #[arg(required = true, value_name = "FILE", help_heading = "Arguments")]
599
615
  pub file: String,
@@ -137,6 +137,7 @@ fn main() {
137
137
  a.ios_triple.as_deref(),
138
138
  &a.crate_type,
139
139
  &a.format,
140
+ &a.jsx_import_source,
140
141
  )
141
142
  }
142
143
  Some(Commands::CompileModule(a)) => compile_module(&a),
@@ -544,6 +545,7 @@ fn compile_to_js(
544
545
  optimize: bool,
545
546
  source_map: bool,
546
547
  format: &str,
548
+ jsx_import_source: &str,
547
549
  ) -> Result<(), String> {
548
550
  if format != "bundle" && format != "esm" {
549
551
  return Err(format!(
@@ -574,7 +576,14 @@ fn compile_to_js(
574
576
  }
575
577
  });
576
578
  if format == "esm" {
577
- return compile_to_js_esm(input_path, output_path, optimize, source_map, project_root);
579
+ return compile_to_js_esm(
580
+ input_path,
581
+ output_path,
582
+ optimize,
583
+ source_map,
584
+ project_root,
585
+ jsx_import_source,
586
+ );
578
587
  }
579
588
  let out_path = Path::new(output_path);
580
589
  let out_path = if out_path.extension().is_none()
@@ -656,6 +665,7 @@ fn compile_to_js_esm(
656
665
  optimize: bool,
657
666
  source_map: bool,
658
667
  project_root: Option<&Path>,
668
+ jsx_import_source: &str,
659
669
  ) -> Result<(), String> {
660
670
  if source_map {
661
671
  return Err(
@@ -671,8 +681,9 @@ fn compile_to_js_esm(
671
681
  .into(),
672
682
  );
673
683
  }
674
- let modules = tishlang_compile_js::compile_project_esm(input_path, project_root, optimize)
675
- .map_err(|e| format!("{}", e))?;
684
+ let modules =
685
+ tishlang_compile_js::compile_project_esm(input_path, project_root, optimize, jsx_import_source)
686
+ .map_err(|e| format!("{}", e))?;
676
687
  let out_dir = Path::new(output_path);
677
688
  fs::create_dir_all(out_dir)
678
689
  .map_err(|e| format!("Cannot create output directory {}: {}", out_dir.display(), e))?;
@@ -753,6 +764,7 @@ fn compile_module(args: &CompileModuleArgs) -> Result<(), String> {
753
764
  optimize,
754
765
  import_rewrite,
755
766
  want_map,
767
+ &args.jsx_import_source,
756
768
  )
757
769
  .map_err(|e| format!("{}", e))?;
758
770
 
@@ -781,6 +793,7 @@ fn build_file(
781
793
  ios_triple: Option<&str>,
782
794
  crate_type: &str,
783
795
  format: &str,
796
+ jsx_import_source: &str,
784
797
  ) -> Result<(), String> {
785
798
  let optimize = !no_optimize;
786
799
  let input_path = Path::new(input_path)
@@ -790,7 +803,14 @@ fn build_file(
790
803
  let is_js = input_path.extension().map(|e| e == "js") == Some(true);
791
804
 
792
805
  if target == "js" {
793
- return compile_to_js(&input_path, output_path, optimize, source_map, format);
806
+ return compile_to_js(
807
+ &input_path,
808
+ output_path,
809
+ optimize,
810
+ source_map,
811
+ format,
812
+ jsx_import_source,
813
+ );
794
814
  }
795
815
 
796
816
  // `wasm-gpu` (#277): same pipeline as `wasm` but builds the `--features gpu` WebGPU runtime and
@@ -52,6 +52,28 @@ fn integration_compile_cache_dir() -> PathBuf {
52
52
  target_dir().join("integration_compile_cache")
53
53
  }
54
54
 
55
+ /// Remove cache entries under `dir` whose name starts with `prefix` but is not part of the current
56
+ /// generation `keep_leaf` (the current artifact may have an extension appended, e.g. `<leaf>.wasm`,
57
+ /// so we keep anything that *starts with* `keep_leaf`). Best-effort: caps the per-fixture compile
58
+ /// cache so a `tish` rebuild doesn't leave the previous generation behind forever. Parallel-safe —
59
+ /// see the call site in `compile_cached`.
60
+ fn prune_stale_cache_generations(dir: &Path, prefix: &str, keep_leaf: &str) {
61
+ if let Ok(rd) = std::fs::read_dir(dir) {
62
+ for ent in rd.flatten() {
63
+ let name = ent.file_name();
64
+ let name = name.to_string_lossy();
65
+ if name.starts_with(prefix) && !name.starts_with(keep_leaf) {
66
+ let p = ent.path();
67
+ if p.is_dir() {
68
+ let _ = std::fs::remove_dir_all(&p);
69
+ } else {
70
+ let _ = std::fs::remove_file(&p);
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+
55
77
  /// Match `tish build` with no `--feature`: link every capability compiled into this `tish` binary.
56
78
  fn native_build_features_for_integration_test() -> Vec<String> {
57
79
  let mut v: Vec<String> = tishlang_vm::all_compiled_capabilities()
@@ -170,6 +192,14 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
170
192
  // Include `backend` in the leaf name so nested cargo bin names never collide across backends.
171
193
  let leaf = format!("{}__{}__{}", stem, backend, hash8);
172
194
 
195
+ // Cap the cache: `hash8` mixes in the `tish` binary's mtime, so every recompile of `tish`
196
+ // orphans the previous generation of THIS (stem, backend). Drop those stale siblings now, so the
197
+ // cache holds ~one generation per fixture instead of growing without bound across rebuilds
198
+ // (it reached 14 GB in one session and exhausted the disk). Safe under parallel `nextest`: every
199
+ // live process for this (stem, backend) computes the SAME `hash8`, so only genuinely-stale
200
+ // generations (a different hash) are removed, never an artifact a concurrent run is using.
201
+ prune_stale_cache_generations(&cache_base, &format!("{}__{}__", stem, backend), &leaf);
202
+
173
203
  let (artifact_path, compile_args): (PathBuf, Vec<OsString>) = match backend {
174
204
  "native" => {
175
205
  let ext = if cfg!(target_os = "windows") {
@@ -588,6 +618,46 @@ fn test_import_alias() {
588
618
  }
589
619
  }
590
620
 
621
+ /// #305: re-export — `export { foo, bar as inc } from "./dep"` and `export * from "./dep"` in a
622
+ /// middle module, consumed by a named import and a namespace import in the entry. Identical across
623
+ /// the interpreter and the VM (native/cranelift/js verified via the bundle path).
624
+ #[test]
625
+ fn test_reexport() {
626
+ let bin = tish_bin();
627
+ if !bin.exists() {
628
+ return;
629
+ }
630
+ let path = workspace_root()
631
+ .join("tests")
632
+ .join("modules")
633
+ .join("reexport.tish");
634
+ if !path.exists() {
635
+ return;
636
+ }
637
+ let expected = "42\n42\n1.0\n42\n";
638
+ for backend_args in [vec!["run"], vec!["run", "--backend", "vm"]] {
639
+ let mut args = backend_args.clone();
640
+ args.push(path.to_string_lossy().to_string().leak());
641
+ let out = Command::new(&bin)
642
+ .args(&args)
643
+ .current_dir(workspace_root())
644
+ .output()
645
+ .expect("run tish binary");
646
+ assert!(
647
+ out.status.success(),
648
+ "reexport.tish ({:?}) failed: stderr={}",
649
+ backend_args,
650
+ String::from_utf8_lossy(&out.stderr)
651
+ );
652
+ assert_eq!(
653
+ String::from_utf8_lossy(&out.stdout),
654
+ expected,
655
+ "reexport output mismatch on backend {:?}",
656
+ backend_args
657
+ );
658
+ }
659
+ }
660
+
591
661
  /// Combined validation: async/await + Promise + setTimeout + multiple HTTP requests.
592
662
  /// #97: a module's non-exported top-level bindings stay private — a same-named binding in
593
663
  /// another module must not overwrite them (runtime), and the `--target js` bundle must not
@@ -846,6 +916,75 @@ fn test_compile_module_no_source_map_prints_raw_js() {
846
916
  );
847
917
  }
848
918
 
919
+ /// #291: `compile-module` auto-imports the JSX runtime (`h` / `Fragment`) when a module uses JSX
920
+ /// but doesn't import them itself, so per-module ESM output doesn't throw `ReferenceError` at load.
921
+ /// `--jsx-import-source` overrides the runtime package.
922
+ #[test]
923
+ fn test_compile_module_jsx_auto_imports_runtime() {
924
+ let bin = tish_bin();
925
+ if !bin.exists() {
926
+ return;
927
+ }
928
+ let widget = workspace_root()
929
+ .join("tests")
930
+ .join("modules")
931
+ .join("esm")
932
+ .join("vite_hmr")
933
+ .join("widget.tish");
934
+ if !widget.exists() {
935
+ return;
936
+ }
937
+ // Default runtime source (`lattish`), Vite-dev so the specifier stays bare.
938
+ let out = Command::new(&bin)
939
+ .args(["compile-module"])
940
+ .arg(&widget)
941
+ .args([
942
+ "--target",
943
+ "js",
944
+ "--format",
945
+ "esm",
946
+ "--vite-dev",
947
+ "--no-source-map",
948
+ ])
949
+ .current_dir(workspace_root())
950
+ .output()
951
+ .expect("run compile-module");
952
+ assert!(
953
+ out.status.success(),
954
+ "compile-module failed: stderr={}",
955
+ String::from_utf8_lossy(&out.stderr)
956
+ );
957
+ let stdout = String::from_utf8_lossy(&out.stdout);
958
+ assert!(
959
+ stdout.contains("import { h, Fragment } from \"lattish\";"),
960
+ "JSX module with a fragment auto-imports both h and Fragment:\n{stdout}"
961
+ );
962
+
963
+ // Custom runtime source via --jsx-import-source.
964
+ let out2 = Command::new(&bin)
965
+ .args(["compile-module"])
966
+ .arg(&widget)
967
+ .args([
968
+ "--target",
969
+ "js",
970
+ "--format",
971
+ "esm",
972
+ "--vite-dev",
973
+ "--no-source-map",
974
+ "--jsx-import-source",
975
+ "@tishlang/lattish",
976
+ ])
977
+ .current_dir(workspace_root())
978
+ .output()
979
+ .expect("run compile-module");
980
+ assert!(out2.status.success());
981
+ let stdout2 = String::from_utf8_lossy(&out2.stdout);
982
+ assert!(
983
+ stdout2.contains("from \"@tishlang/lattish\";"),
984
+ "--jsx-import-source overrides the runtime package:\n{stdout2}"
985
+ );
986
+ }
987
+
849
988
  /// Ignored: tishlang_eval::run() does not run the event loop.
850
989
  #[test]
851
990
  #[cfg(feature = "http")]
@@ -1307,6 +1446,16 @@ fn test_mvp_programs_native() {
1307
1446
  let combined = combined_mvp_native_inputs_hash(&paths);
1308
1447
  let cache_dir = mvp_native_batch_cache_dir(combined);
1309
1448
  let _ = std::fs::create_dir_all(&cache_dir);
1449
+ // Cap the native batch cache: a new combined hash (a codegen / inference / source change) orphans
1450
+ // the previous `native_many/<hash>` directory, each of which holds a full nested Cargo `target/`
1451
+ // (~GBs). Drop the stale siblings, keeping only the current generation. Only `test_mvp_programs_
1452
+ // native` builds here, so there is no cross-process race.
1453
+ if let (Some(parent), Some(keep)) = (
1454
+ cache_dir.parent(),
1455
+ cache_dir.file_name().map(|n| n.to_string_lossy().into_owned()),
1456
+ ) {
1457
+ prune_stale_cache_generations(parent, "", &keep);
1458
+ }
1310
1459
 
1311
1460
  let ext = if cfg!(target_os = "windows") {
1312
1461
  ".exe"
@@ -156,6 +156,16 @@ pub enum ExportDeclaration {
156
156
  Named(Box<Statement>),
157
157
  /// export default expr
158
158
  Default(Expr),
159
+ /// Re-export from another module (#305):
160
+ /// `export { foo, bar as inc } from "./m"` -> specifiers, all=false
161
+ /// `export * from "./m"` -> specifiers=[], all=true
162
+ /// Specifiers reuse `ImportSpecifier::Named` (`{ a as b }`).
163
+ ReExport {
164
+ specifiers: Vec<ImportSpecifier>,
165
+ all: bool,
166
+ from: Arc<str>,
167
+ span: Span,
168
+ },
159
169
  }
160
170
 
161
171
  #[derive(Debug, Clone)]