@tishlang/tish 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/Cargo.toml +2 -0
  2. package/README.md +2 -0
  3. package/bin/tish +0 -0
  4. package/crates/js_to_tish/src/error.rs +2 -8
  5. package/crates/js_to_tish/src/transform/expr.rs +128 -137
  6. package/crates/js_to_tish/src/transform/stmt.rs +62 -32
  7. package/crates/tish/Cargo.toml +15 -5
  8. package/crates/tish/src/cargo_native_registry.rs +29 -0
  9. package/crates/tish/src/cli_help.rs +92 -39
  10. package/crates/tish/src/main.rs +172 -86
  11. package/crates/tish/src/repl_completion.rs +3 -3
  12. package/crates/tish/tests/cargo_example_compile.rs +4 -2
  13. package/crates/tish/tests/integration_test.rs +216 -54
  14. package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
  15. package/crates/tish/tests/shortcircuit.rs +20 -5
  16. package/crates/tish_ast/src/ast.rs +92 -23
  17. package/crates/tish_build_utils/Cargo.toml +4 -0
  18. package/crates/tish_build_utils/src/lib.rs +136 -8
  19. package/crates/tish_builtins/Cargo.toml +5 -1
  20. package/crates/tish_builtins/src/array.rs +65 -33
  21. package/crates/tish_builtins/src/construct.rs +34 -39
  22. package/crates/tish_builtins/src/globals.rs +42 -26
  23. package/crates/tish_builtins/src/helpers.rs +2 -1
  24. package/crates/tish_builtins/src/lib.rs +5 -5
  25. package/crates/tish_builtins/src/math.rs +5 -3
  26. package/crates/tish_builtins/src/object.rs +3 -2
  27. package/crates/tish_builtins/src/string.rs +144 -22
  28. package/crates/tish_bytecode/src/chunk.rs +0 -1
  29. package/crates/tish_bytecode/src/compiler.rs +173 -71
  30. package/crates/tish_bytecode/src/opcode.rs +24 -6
  31. package/crates/tish_bytecode/src/peephole.rs +2 -2
  32. package/crates/tish_compile/Cargo.toml +1 -0
  33. package/crates/tish_compile/src/codegen.rs +1621 -453
  34. package/crates/tish_compile/src/infer.rs +75 -19
  35. package/crates/tish_compile/src/lib.rs +19 -8
  36. package/crates/tish_compile/src/resolve.rs +278 -137
  37. package/crates/tish_compile/src/types.rs +184 -24
  38. package/crates/tish_compile_js/Cargo.toml +1 -0
  39. package/crates/tish_compile_js/src/codegen.rs +181 -37
  40. package/crates/tish_compile_js/src/lib.rs +3 -1
  41. package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
  42. package/crates/tish_compiler_wasm/src/lib.rs +16 -13
  43. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
  44. package/crates/tish_core/Cargo.toml +8 -0
  45. package/crates/tish_core/src/json.rs +107 -56
  46. package/crates/tish_core/src/lib.rs +4 -2
  47. package/crates/tish_core/src/macros.rs +5 -5
  48. package/crates/tish_core/src/uri.rs +9 -6
  49. package/crates/tish_core/src/value.rs +145 -43
  50. package/crates/tish_core/src/vmref.rs +178 -0
  51. package/crates/tish_cranelift/src/link.rs +6 -9
  52. package/crates/tish_cranelift/src/lower.rs +14 -8
  53. package/crates/tish_eval/Cargo.toml +17 -2
  54. package/crates/tish_eval/src/eval.rs +474 -165
  55. package/crates/tish_eval/src/http.rs +61 -0
  56. package/crates/tish_eval/src/lib.rs +12 -8
  57. package/crates/tish_eval/src/natives.rs +136 -38
  58. package/crates/tish_eval/src/promise.rs +14 -8
  59. package/crates/tish_eval/src/timers.rs +28 -19
  60. package/crates/tish_eval/src/value.rs +17 -6
  61. package/crates/tish_eval/src/value_convert.rs +13 -5
  62. package/crates/tish_fmt/src/lib.rs +149 -43
  63. package/crates/tish_lexer/src/lib.rs +232 -63
  64. package/crates/tish_lexer/src/token.rs +10 -6
  65. package/crates/tish_llvm/src/lib.rs +17 -8
  66. package/crates/tish_lsp/Cargo.toml +4 -1
  67. package/crates/tish_lsp/README.md +1 -1
  68. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  69. package/crates/tish_lsp/src/import_goto.rs +549 -0
  70. package/crates/tish_lsp/src/main.rs +504 -106
  71. package/crates/tish_native/src/build.rs +4 -8
  72. package/crates/tish_native/src/lib.rs +54 -21
  73. package/crates/tish_opt/src/lib.rs +84 -52
  74. package/crates/tish_parser/src/lib.rs +45 -13
  75. package/crates/tish_parser/src/parser.rs +505 -130
  76. package/crates/tish_resolve/Cargo.toml +13 -0
  77. package/crates/tish_resolve/src/lib.rs +3436 -0
  78. package/crates/tish_resolve/src/pos.rs +133 -0
  79. package/crates/tish_runtime/Cargo.toml +68 -3
  80. package/crates/tish_runtime/src/http.rs +1136 -145
  81. package/crates/tish_runtime/src/http_fetch.rs +38 -27
  82. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  83. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  84. package/crates/tish_runtime/src/lib.rs +375 -189
  85. package/crates/tish_runtime/src/promise.rs +199 -40
  86. package/crates/tish_runtime/src/promise_io.rs +2 -1
  87. package/crates/tish_runtime/src/timers.rs +37 -1
  88. package/crates/tish_runtime/src/ws.rs +65 -42
  89. package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
  90. package/crates/tish_ui/src/jsx.rs +317 -27
  91. package/crates/tish_ui/src/lib.rs +5 -2
  92. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  93. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  94. package/crates/tish_vm/Cargo.toml +15 -5
  95. package/crates/tish_vm/src/vm.rs +725 -281
  96. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
  97. package/crates/tish_wasm/src/lib.rs +55 -42
  98. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  99. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  100. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  101. package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
  102. package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
  103. package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
  104. package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
  105. package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
  106. package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
  107. package/justfile +8 -0
  108. package/package.json +1 -1
  109. package/platform/darwin-arm64/tish +0 -0
  110. package/platform/darwin-x64/tish +0 -0
  111. package/platform/linux-arm64/tish +0 -0
  112. package/platform/linux-x64/tish +0 -0
  113. package/platform/win32-x64/tish.exe +0 -0
@@ -7,16 +7,18 @@
7
7
  //! - Compiled outputs are cached under `target/integration_compile_cache/` per backend.
8
8
 
9
9
  use std::collections::hash_map::DefaultHasher;
10
+ use std::ffi::OsString;
10
11
  use std::hash::{Hash, Hasher};
11
12
  use std::io::Read;
12
- use std::ffi::OsString;
13
13
  use std::path::{Path, PathBuf};
14
14
  use std::process::Command;
15
15
 
16
16
  use rayon::prelude::*;
17
17
 
18
18
  fn workspace_root() -> PathBuf {
19
- PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..").join("..")
19
+ PathBuf::from(env!("CARGO_MANIFEST_DIR"))
20
+ .join("..")
21
+ .join("..")
20
22
  }
21
23
 
22
24
  fn core_dir() -> PathBuf {
@@ -25,7 +27,10 @@ fn core_dir() -> PathBuf {
25
27
 
26
28
  /// Path to the static expected stdout for a .tish file (e.g. fn_any.tish -> fn_any.tish.expected).
27
29
  fn expected_path(path: &Path) -> PathBuf {
28
- path.with_file_name(format!("{}.expected", path.file_name().unwrap().to_string_lossy()))
30
+ path.with_file_name(format!(
31
+ "{}.expected",
32
+ path.file_name().unwrap().to_string_lossy()
33
+ ))
29
34
  }
30
35
 
31
36
  /// Read static expected stdout for a test file. Returns None if the file does not exist.
@@ -61,17 +66,29 @@ fn file_content_hash(path: &Path) -> u64 {
61
66
  ///
62
67
  /// Cache is keyed by backend (native, cranelift, js, wasi) so e.g. cranelift and wasi
63
68
  /// compiles of the same file do not overwrite each other: .../cranelift/<stem>_<hash> vs .../wasi/<stem>_<hash>.wasm.
69
+ ///
70
+ /// The artifact **basename** must be unique per `(stem, hash, backend)`: nested `tish build`
71
+ /// uses it as the Cargo binary name under the workspace `target/release/`. If native and
72
+ /// cranelift both used `strict_equality_<hash>`, parallel `cargo nextest` could run those
73
+ /// tests concurrently and corrupt the same `target/release/...` output (Linux: ETXTBSY when
74
+ /// executing a binary still being written).
64
75
  fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
65
76
  let stem = path.file_stem().unwrap().to_string_lossy();
66
77
  let hash = file_content_hash(path);
67
78
  let hash8 = &format!("{:016x}", hash)[..8];
68
79
  let cache_base = integration_compile_cache_dir().join(backend);
69
80
  let _ = std::fs::create_dir_all(&cache_base);
81
+ // Include `backend` in the leaf name so nested cargo bin names never collide across backends.
82
+ let leaf = format!("{}__{}__{}", stem, backend, hash8);
70
83
 
71
84
  let (artifact_path, compile_args): (PathBuf, Vec<OsString>) = match backend {
72
85
  "native" => {
73
- let ext = if cfg!(target_os = "windows") { ".exe" } else { "" };
74
- let cached = cache_base.join(format!("{}_{}{}", stem, hash8, ext));
86
+ let ext = if cfg!(target_os = "windows") {
87
+ ".exe"
88
+ } else {
89
+ ""
90
+ };
91
+ let cached = cache_base.join(format!("{}{}", leaf, ext));
75
92
  let args = vec![
76
93
  OsString::from("build"),
77
94
  OsString::from(path),
@@ -81,8 +98,12 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
81
98
  (cached, args)
82
99
  }
83
100
  "cranelift" => {
84
- let ext = if cfg!(target_os = "windows") { ".exe" } else { "" };
85
- let cached = cache_base.join(format!("{}_{}{}", stem, hash8, ext));
101
+ let ext = if cfg!(target_os = "windows") {
102
+ ".exe"
103
+ } else {
104
+ ""
105
+ };
106
+ let cached = cache_base.join(format!("{}{}", leaf, ext));
86
107
  let args = vec![
87
108
  OsString::from("build"),
88
109
  OsString::from(path),
@@ -94,7 +115,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
94
115
  (cached, args)
95
116
  }
96
117
  "js" => {
97
- let cached = cache_base.join(format!("{}_{}.js", stem, hash8));
118
+ let cached = cache_base.join(format!("{}.js", leaf));
98
119
  let args = vec![
99
120
  OsString::from("build"),
100
121
  OsString::from(path),
@@ -106,7 +127,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
106
127
  (cached, args)
107
128
  }
108
129
  "wasi" => {
109
- let out_base = cache_base.join(format!("{}_{}", stem, hash8));
130
+ let out_base = cache_base.join(&leaf);
110
131
  let artifact = out_base.with_extension("wasm");
111
132
  let args = vec![
112
133
  OsString::from("build"),
@@ -137,8 +158,12 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
137
158
  }
138
159
 
139
160
  // Copy to temp so caller can run and delete without touching cache.
140
- let ext = artifact_path.extension().map(|e| e.to_string_lossy().to_string()).unwrap_or_default();
141
- let temp_dest = std::env::temp_dir().join(format!("tish_cached_{}_{}_{}", backend, stem, hash8));
161
+ let ext = artifact_path
162
+ .extension()
163
+ .map(|e| e.to_string_lossy().to_string())
164
+ .unwrap_or_default();
165
+ let temp_dest =
166
+ std::env::temp_dir().join(format!("tish_cached_{}_{}_{}", backend, stem, hash8));
142
167
  let temp_dest = if ext.is_empty() {
143
168
  temp_dest
144
169
  } else {
@@ -151,12 +176,20 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
151
176
  /// Path to the tish CLI binary. When running under cargo-llvm-cov, the build goes to
152
177
  /// target/llvm-cov-target and CARGO_TARGET_DIR may not be set for the test process.
153
178
  fn tish_bin() -> PathBuf {
154
- let bin_name = if cfg!(target_os = "windows") { "tish.exe" } else { "tish" };
179
+ let bin_name = if cfg!(target_os = "windows") {
180
+ "tish.exe"
181
+ } else {
182
+ "tish"
183
+ };
155
184
  let default = target_dir().join("debug").join(bin_name);
156
185
  if default.exists() {
157
186
  return default;
158
187
  }
159
- let llvm_cov = workspace_root().join("target").join("llvm-cov-target").join("debug").join(bin_name);
188
+ let llvm_cov = workspace_root()
189
+ .join("target")
190
+ .join("llvm-cov-target")
191
+ .join("debug")
192
+ .join(bin_name);
160
193
  if llvm_cov.exists() {
161
194
  return llvm_cov;
162
195
  }
@@ -167,9 +200,16 @@ fn tish_bin() -> PathBuf {
167
200
  #[test]
168
201
  fn test_tish_version_flag() {
169
202
  let bin = tish_bin();
170
- assert!(bin.exists(), "tish binary not found. Run `cargo build -p tishlang` first.");
203
+ assert!(
204
+ bin.exists(),
205
+ "tish binary not found. Run `cargo build -p tishlang` first."
206
+ );
171
207
  let out = Command::new(&bin).arg("-V").output().expect("run tish -V");
172
- assert!(out.status.success(), "tish -V failed: {}", String::from_utf8_lossy(&out.stderr));
208
+ assert!(
209
+ out.status.success(),
210
+ "tish -V failed: {}",
211
+ String::from_utf8_lossy(&out.stderr)
212
+ );
173
213
  let stdout = String::from_utf8_lossy(&out.stdout);
174
214
  assert!(
175
215
  stdout.contains(env!("CARGO_PKG_VERSION")),
@@ -177,20 +217,35 @@ fn test_tish_version_flag() {
177
217
  env!("CARGO_PKG_VERSION"),
178
218
  stdout
179
219
  );
180
- let out2 = Command::new(&bin).arg("--version").output().expect("run tish --version");
220
+ let out2 = Command::new(&bin)
221
+ .arg("--version")
222
+ .output()
223
+ .expect("run tish --version");
181
224
  assert!(out2.status.success());
182
225
  let stdout2 = String::from_utf8_lossy(&out2.stdout);
183
- assert!(stdout2.contains(env!("CARGO_PKG_VERSION")), "tish --version should print version");
226
+ assert!(
227
+ stdout2.contains(env!("CARGO_PKG_VERSION")),
228
+ "tish --version should print version"
229
+ );
184
230
  }
185
231
 
186
232
  /// Parse async-await example (validates async fn parsing).
187
233
  #[test]
188
234
  fn test_async_await_parse() {
189
- let path = workspace_root().join("examples").join("async-await").join("src").join("main.tish");
235
+ let path = workspace_root()
236
+ .join("examples")
237
+ .join("async-await")
238
+ .join("src")
239
+ .join("main.tish");
190
240
  if path.exists() {
191
241
  let source = std::fs::read_to_string(&path).unwrap();
192
242
  let result = tishlang_parser::parse(&source);
193
- assert!(result.is_ok(), "Parse failed for {}: {:?}", path.display(), result.err());
243
+ assert!(
244
+ result.is_ok(),
245
+ "Parse failed for {}: {:?}",
246
+ path.display(),
247
+ result.err()
248
+ );
194
249
  }
195
250
  }
196
251
 
@@ -199,11 +254,20 @@ fn test_async_await_parse() {
199
254
  #[cfg(feature = "http")]
200
255
  fn test_async_await_compile_via_binary() {
201
256
  let bin = tish_bin();
202
- let path = workspace_root().join("examples").join("async-await").join("src").join("main.tish");
257
+ let path = workspace_root()
258
+ .join("examples")
259
+ .join("async-await")
260
+ .join("src")
261
+ .join("main.tish");
203
262
  if path.exists() && bin.exists() {
204
263
  let out = std::env::temp_dir().join("tish_async_test_out");
205
264
  let compile_result = Command::new(&bin)
206
- .args(["build", path.to_string_lossy().as_ref(), "-o", out.to_string_lossy().as_ref()])
265
+ .args([
266
+ "build",
267
+ path.to_string_lossy().as_ref(),
268
+ "-o",
269
+ out.to_string_lossy().as_ref(),
270
+ ])
207
271
  .current_dir(workspace_root())
208
272
  .output();
209
273
  let compile_out = compile_result.expect("run tish build");
@@ -213,9 +277,7 @@ fn test_async_await_compile_via_binary() {
213
277
  String::from_utf8_lossy(&compile_out.stderr)
214
278
  );
215
279
  // Run compiled binary to validate non-blocking fetchAll executes correctly
216
- let run_result = Command::new(&out)
217
- .current_dir(workspace_root())
218
- .output();
280
+ let run_result = Command::new(&out).current_dir(workspace_root()).output();
219
281
  let run_out = run_result.expect("run compiled async binary");
220
282
  assert!(
221
283
  run_out.status.success(),
@@ -223,7 +285,10 @@ fn test_async_await_compile_via_binary() {
223
285
  String::from_utf8_lossy(&run_out.stderr)
224
286
  );
225
287
  let stdout = String::from_utf8_lossy(&run_out.stdout);
226
- assert!(stdout.contains("Fetching"), "expected output to mention fetching");
288
+ assert!(
289
+ stdout.contains("Fetching"),
290
+ "expected output to mention fetching"
291
+ );
227
292
  assert!(stdout.contains("Done"), "expected output to contain Done");
228
293
  }
229
294
  }
@@ -235,8 +300,16 @@ fn test_async_await_compile_via_binary() {
235
300
  #[ignore = "timing and network sensitive; run manually: cargo test test_async_parallel_vs_sequential_timing -p tishlang--features http -- --ignored"]
236
301
  fn test_async_parallel_vs_sequential_timing() {
237
302
  let bin = tish_bin();
238
- let parallel_src = workspace_root().join("examples").join("async-await").join("src").join("parallel.tish");
239
- let sequential_src = workspace_root().join("examples").join("async-await").join("src").join("sequential.tish");
303
+ let parallel_src = workspace_root()
304
+ .join("examples")
305
+ .join("async-await")
306
+ .join("src")
307
+ .join("parallel.tish");
308
+ let sequential_src = workspace_root()
309
+ .join("examples")
310
+ .join("async-await")
311
+ .join("src")
312
+ .join("sequential.tish");
240
313
  if !parallel_src.exists() || !sequential_src.exists() || !bin.exists() {
241
314
  return;
242
315
  }
@@ -245,28 +318,58 @@ fn test_async_parallel_vs_sequential_timing() {
245
318
 
246
319
  // Compile both
247
320
  let compile_par = Command::new(&bin)
248
- .args(["build", parallel_src.to_string_lossy().as_ref(), "-o", out_parallel.to_string_lossy().as_ref()])
321
+ .args([
322
+ "build",
323
+ parallel_src.to_string_lossy().as_ref(),
324
+ "-o",
325
+ out_parallel.to_string_lossy().as_ref(),
326
+ ])
249
327
  .current_dir(workspace_root())
250
328
  .output();
251
- assert!(compile_par.as_ref().unwrap().status.success(), "compile parallel: {}", String::from_utf8_lossy(&compile_par.as_ref().unwrap().stderr));
329
+ assert!(
330
+ compile_par.as_ref().unwrap().status.success(),
331
+ "compile parallel: {}",
332
+ String::from_utf8_lossy(&compile_par.as_ref().unwrap().stderr)
333
+ );
252
334
 
253
335
  let compile_seq = Command::new(&bin)
254
- .args(["build", sequential_src.to_string_lossy().as_ref(), "-o", out_sequential.to_string_lossy().as_ref()])
336
+ .args([
337
+ "build",
338
+ sequential_src.to_string_lossy().as_ref(),
339
+ "-o",
340
+ out_sequential.to_string_lossy().as_ref(),
341
+ ])
255
342
  .current_dir(workspace_root())
256
343
  .output();
257
- assert!(compile_seq.as_ref().unwrap().status.success(), "compile sequential: {}", String::from_utf8_lossy(&compile_seq.as_ref().unwrap().stderr));
344
+ assert!(
345
+ compile_seq.as_ref().unwrap().status.success(),
346
+ "compile sequential: {}",
347
+ String::from_utf8_lossy(&compile_seq.as_ref().unwrap().stderr)
348
+ );
258
349
 
259
350
  // Run parallel and time
260
351
  let t_parallel = std::time::Instant::now();
261
- let run_par = Command::new(&out_parallel).current_dir(workspace_root()).output();
352
+ let run_par = Command::new(&out_parallel)
353
+ .current_dir(workspace_root())
354
+ .output();
262
355
  let elapsed_parallel = t_parallel.elapsed();
263
- assert!(run_par.as_ref().unwrap().status.success(), "run parallel: {}", String::from_utf8_lossy(&run_par.as_ref().unwrap().stderr));
356
+ assert!(
357
+ run_par.as_ref().unwrap().status.success(),
358
+ "run parallel: {}",
359
+ String::from_utf8_lossy(&run_par.as_ref().unwrap().stderr)
360
+ );
264
361
 
265
362
  // Run sequential and time
266
363
  let t_sequential = std::time::Instant::now();
267
- let run_seq = Command::new(&out_sequential).current_dir(workspace_root()).output();
364
+ let run_seq = Command::new(&out_sequential)
365
+ .current_dir(workspace_root())
366
+ .output();
268
367
  let elapsed_sequential = t_sequential.elapsed();
269
- assert!(run_seq.as_ref().unwrap().status.success(), "run sequential: {}", String::from_utf8_lossy(&run_seq.as_ref().unwrap().stderr));
368
+ assert!(
369
+ run_seq.as_ref().unwrap().status.success(),
370
+ "run sequential: {}",
371
+ String::from_utf8_lossy(&run_seq.as_ref().unwrap().stderr)
372
+ );
270
373
 
271
374
  // PARALLEL MUST BE FASTER: parallel < sequential * 0.6 (parallel ~1s, sequential ~3s)
272
375
  let parallel_secs = elapsed_parallel.as_secs_f64();
@@ -285,22 +388,34 @@ fn test_async_parallel_vs_sequential_timing() {
285
388
  #[cfg(feature = "http")]
286
389
  #[ignore = "requires async runtime; use test_async_await_compile_via_binary for CI"]
287
390
  fn test_async_await_run() {
288
- let path = workspace_root().join("examples").join("async-await").join("src").join("main.tish");
391
+ let path = workspace_root()
392
+ .join("examples")
393
+ .join("async-await")
394
+ .join("src")
395
+ .join("main.tish");
289
396
  if path.exists() {
290
397
  let source = std::fs::read_to_string(&path).unwrap();
291
398
  let result = tishlang_eval::run(&source);
292
- assert!(result.is_ok(), "Run failed for {}: {:?}", path.display(), result.err());
399
+ assert!(
400
+ result.is_ok(),
401
+ "Run failed for {}: {:?}",
402
+ path.display(),
403
+ result.err()
404
+ );
293
405
  }
294
406
  }
295
407
 
296
- /// Run Promise and setTimeout module tests (require http feature).
408
+ /// Run Promise and setTimeout module tests (`promise` needs `http`; `settimeout` needs `timers`, which `http` enables).
297
409
  /// Ignored: tishlang_eval::run() does not run the event loop.
298
410
  #[test]
299
411
  #[cfg(feature = "http")]
300
412
  #[ignore = "requires async runtime"]
301
413
  fn test_promise_and_settimeout() {
302
414
  for name in ["promise", "settimeout"] {
303
- let path = workspace_root().join("tests").join("modules").join(format!("{}.tish", name));
415
+ let path = workspace_root()
416
+ .join("tests")
417
+ .join("modules")
418
+ .join(format!("{}.tish", name));
304
419
  if path.exists() {
305
420
  let source = std::fs::read_to_string(&path).unwrap();
306
421
  let result = tishlang_eval::run(&source);
@@ -338,17 +453,26 @@ fn test_async_promise_settimeout_combined() {
338
453
  /// VM run with Date global (resolve+merge+bytecode+run pipeline).
339
454
  #[test]
340
455
  fn test_vm_date_now() {
341
- let path = workspace_root().join("tests").join("core").join("date.tish");
456
+ let path = workspace_root()
457
+ .join("tests")
458
+ .join("core")
459
+ .join("date.tish");
342
460
  if !path.exists() {
343
461
  return;
344
462
  }
345
463
  // Library path
346
464
  let modules = tishlang_compile::resolve_project(&path, path.parent()).expect("resolve");
347
465
  tishlang_compile::detect_cycles(&modules).expect("cycles");
348
- let program = tishlang_compile::merge_modules(modules).expect("merge");
466
+ let program = tishlang_compile::merge_modules(modules)
467
+ .expect("merge")
468
+ .program;
349
469
  let chunk = tishlang_bytecode::compile(&program).expect("compile");
350
470
  let result = tishlang_vm::run(&chunk);
351
- assert!(result.is_ok(), "VM run (library) failed: {:?}", result.err());
471
+ assert!(
472
+ result.is_ok(),
473
+ "VM run (library) failed: {:?}",
474
+ result.err()
475
+ );
352
476
  // Binary path - same flow as `tish run <file>`
353
477
  let bin = tish_bin();
354
478
  if bin.exists() {
@@ -379,20 +503,32 @@ fn test_vm_index_assign_direct() {
379
503
  /// VM run via resolve+merge (same as tish run) - must also pass.
380
504
  #[test]
381
505
  fn test_vm_index_assign_via_resolve() {
382
- let path = workspace_root().join("tests").join("core").join("array_sort_minimal.tish");
506
+ let path = workspace_root()
507
+ .join("tests")
508
+ .join("core")
509
+ .join("array_sort_minimal.tish");
383
510
  let modules = tishlang_compile::resolve_project(&path, path.parent()).expect("resolve");
384
511
  tishlang_compile::detect_cycles(&modules).expect("cycles");
385
- let program = tishlang_compile::merge_modules(modules).expect("merge");
512
+ let program = tishlang_compile::merge_modules(modules)
513
+ .expect("merge")
514
+ .program;
386
515
  let chunk = tishlang_bytecode::compile(&program).expect("compile");
387
516
  let result = tishlang_vm::run(&chunk);
388
- assert!(result.is_ok(), "VM IndexAssign via resolve failed: {:?}", result.err());
517
+ assert!(
518
+ result.is_ok(),
519
+ "VM IndexAssign via resolve failed: {:?}",
520
+ result.err()
521
+ );
389
522
  }
390
523
 
391
524
  /// tish run binary must pass array_sort_minimal (ensures CLI works).
392
525
  #[test]
393
526
  fn test_tish_run_index_assign() {
394
527
  let bin = tish_bin();
395
- let path = workspace_root().join("tests").join("core").join("array_sort_minimal.tish");
528
+ let path = workspace_root()
529
+ .join("tests")
530
+ .join("core")
531
+ .join("array_sort_minimal.tish");
396
532
  if !bin.exists() {
397
533
  eprintln!("Skipping: tish binary not built");
398
534
  return;
@@ -516,7 +652,12 @@ fn test_mvp_programs_interpreter() {
516
652
  path.display()
517
653
  )
518
654
  });
519
- assert_eq!(stdout, expected, "Interpreter output mismatch for {}", path.display());
655
+ assert_eq!(
656
+ stdout,
657
+ expected,
658
+ "Interpreter output mismatch for {}",
659
+ path.display()
660
+ );
520
661
  }
521
662
  }
522
663
  }
@@ -562,7 +703,8 @@ fn test_mvp_programs_interp_vm_stdout_parity() {
562
703
  let s_interp = String::from_utf8_lossy(&out_interp.stdout);
563
704
  let s_vm = String::from_utf8_lossy(&out_vm.stdout);
564
705
  assert_eq!(
565
- s_interp, s_vm,
706
+ s_interp,
707
+ s_vm,
566
708
  "interp vs VM stdout mismatch for {}",
567
709
  path.display()
568
710
  );
@@ -603,7 +745,11 @@ fn test_mvp_programs_native() {
603
745
  };
604
746
  let _ = std::fs::remove_file(&out_bin);
605
747
  if !out.status.success() {
606
- return Some(format!("{}: {}", path.display(), String::from_utf8_lossy(&out.stderr)));
748
+ return Some(format!(
749
+ "{}: {}",
750
+ path.display(),
751
+ String::from_utf8_lossy(&out.stderr)
752
+ ));
607
753
  }
608
754
  let stdout = String::from_utf8_lossy(&out.stdout);
609
755
  if stdout != expected {
@@ -670,7 +816,11 @@ fn test_mvp_programs_cranelift() {
670
816
  };
671
817
  let _ = std::fs::remove_file(&out_bin);
672
818
  if !out.status.success() {
673
- return Some(format!("{}: {}", path.display(), String::from_utf8_lossy(&out.stderr)));
819
+ return Some(format!(
820
+ "{}: {}",
821
+ path.display(),
822
+ String::from_utf8_lossy(&out.stderr)
823
+ ));
674
824
  }
675
825
  let stdout = String::from_utf8_lossy(&out.stdout);
676
826
  if stdout != expected {
@@ -679,7 +829,11 @@ fn test_mvp_programs_cranelift() {
679
829
  None
680
830
  })
681
831
  .collect();
682
- assert!(errors.is_empty(), "cranelift failures:\n{}", errors.join("\n"));
832
+ assert!(
833
+ errors.is_empty(),
834
+ "cranelift failures:\n{}",
835
+ errors.join("\n")
836
+ );
683
837
  }
684
838
 
685
839
  /// Compile each .tish file to WASI, run with wasmtime, and compare stdout to static expected (parallelized).
@@ -727,7 +881,11 @@ fn test_mvp_programs_wasi() {
727
881
  };
728
882
  let _ = std::fs::remove_file(&out_wasm);
729
883
  if !out.status.success() {
730
- return Some(format!("{}: {}", path.display(), String::from_utf8_lossy(&out.stderr)));
884
+ return Some(format!(
885
+ "{}: {}",
886
+ path.display(),
887
+ String::from_utf8_lossy(&out.stderr)
888
+ ));
731
889
  }
732
890
  let stdout = String::from_utf8_lossy(&out.stdout);
733
891
  if stdout != expected {
@@ -789,7 +947,11 @@ fn test_mvp_programs_js() {
789
947
  String::from_utf8_lossy(&out.stderr)
790
948
  );
791
949
  let stdout = String::from_utf8_lossy(&out.stdout);
792
- assert_eq!(stdout, expected, "JS output mismatch for {}", path.display());
950
+ assert_eq!(
951
+ stdout,
952
+ expected,
953
+ "JS output mismatch for {}",
954
+ path.display()
955
+ );
793
956
  }
794
957
  }
795
-
@@ -15,12 +15,7 @@ fn string_or_fixture_stdout_matches_with_and_without_optimize() {
15
15
  assert!(fixture.is_file(), "missing fixture {}", fixture.display());
16
16
 
17
17
  let out_default = Command::new(&tish)
18
- .args([
19
- "run",
20
- "--feature",
21
- "process",
22
- fixture.to_str().unwrap(),
23
- ])
18
+ .args(["run", "--feature", "process", fixture.to_str().unwrap()])
24
19
  .output()
25
20
  .expect("spawn tish run");
26
21
  assert!(
@@ -46,7 +41,8 @@ fn string_or_fixture_stdout_matches_with_and_without_optimize() {
46
41
  );
47
42
 
48
43
  assert_eq!(
49
- out_default.stdout, out_noopt.stdout,
44
+ out_default.stdout,
45
+ out_noopt.stdout,
50
46
  "stdout differs:\n default: {:?}\n noopt: {:?}",
51
47
  String::from_utf8_lossy(&out_default.stdout),
52
48
  String::from_utf8_lossy(&out_noopt.stdout)
@@ -14,7 +14,10 @@ fn test_and_shortcircuit_emits_jump() {
14
14
  let chunk = compile_unoptimized(&program).expect("compile");
15
15
  let code = &chunk.code;
16
16
  let has_jump_if_false = code.windows(1).any(|w| w[0] == Opcode::JumpIfFalse as u8);
17
- assert!(has_jump_if_false, "And should emit JumpIfFalse for short-circuit");
17
+ assert!(
18
+ has_jump_if_false,
19
+ "And should emit JumpIfFalse for short-circuit"
20
+ );
18
21
  }
19
22
 
20
23
  #[test]
@@ -23,7 +26,11 @@ fn test_and_shortcircuit_runs_unoptimized() {
23
26
  let program = parse(source).expect("parse");
24
27
  let chunk = compile_unoptimized(&program).expect("compile");
25
28
  let result = tishlang_vm::run(&chunk);
26
- assert!(result.is_ok(), "Should not throw (short-circuit avoids x.foo): {:?}", result.err());
29
+ assert!(
30
+ result.is_ok(),
31
+ "Should not throw (short-circuit avoids x.foo): {:?}",
32
+ result.err()
33
+ );
27
34
  }
28
35
 
29
36
  #[test]
@@ -33,7 +40,11 @@ fn test_and_shortcircuit_runs_optimized() {
33
40
  let program = tishlang_opt::optimize(&program);
34
41
  let chunk = tishlang_bytecode::compile(&program).expect("compile");
35
42
  let result = tishlang_vm::run(&chunk);
36
- assert!(result.is_ok(), "Should not throw with peephole (short-circuit): {:?}", result.err());
43
+ assert!(
44
+ result.is_ok(),
45
+ "Should not throw with peephole (short-circuit): {:?}",
46
+ result.err()
47
+ );
37
48
  }
38
49
 
39
50
  #[test]
@@ -42,9 +53,13 @@ fn test_and_shortcircuit_via_resolve_project() {
42
53
  let path = path.canonicalize().expect("path");
43
54
  let project_root = path.parent().unwrap();
44
55
  let modules = resolve_project(&path, Some(project_root)).expect("resolve");
45
- let program = merge_modules(modules).expect("merge");
56
+ let program = merge_modules(modules).expect("merge").program;
46
57
  let program = tishlang_opt::optimize(&program); // Mirror CLI
47
58
  let chunk = compile(&program).expect("compile");
48
59
  let result = tishlang_vm::run(&chunk);
49
- assert!(result.is_ok(), "Should not throw via resolve+merge+opt (CLI path): {:?}", result.err());
60
+ assert!(
61
+ result.is_ok(),
62
+ "Should not throw via resolve+merge+opt (CLI path): {:?}",
63
+ result.err()
64
+ );
50
65
  }