@tishlang/tish 1.9.1 → 1.10.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 (79) hide show
  1. package/bin/tish +0 -0
  2. package/crates/js_to_tish/src/transform/expr.rs +8 -6
  3. package/crates/js_to_tish/src/transform/stmt.rs +12 -13
  4. package/crates/tish/Cargo.toml +1 -1
  5. package/crates/tish/src/cargo_native_registry.rs +4 -1
  6. package/crates/tish/src/main.rs +11 -8
  7. package/crates/tish/tests/integration_test.rs +145 -7
  8. package/crates/tish_ast/src/ast.rs +3 -9
  9. package/crates/tish_build_utils/src/lib.rs +43 -15
  10. package/crates/tish_builtins/src/array.rs +2 -3
  11. package/crates/tish_builtins/src/construct.rs +15 -28
  12. package/crates/tish_builtins/src/globals.rs +18 -16
  13. package/crates/tish_builtins/src/helpers.rs +1 -4
  14. package/crates/tish_builtins/src/lib.rs +1 -0
  15. package/crates/tish_builtins/src/object.rs +10 -10
  16. package/crates/tish_builtins/src/string.rs +1 -3
  17. package/crates/tish_builtins/src/symbol.rs +83 -0
  18. package/crates/tish_compile/src/codegen.rs +123 -138
  19. package/crates/tish_compile/src/lib.rs +25 -3
  20. package/crates/tish_compile/src/resolve.rs +6 -3
  21. package/crates/tish_compile/src/types.rs +6 -6
  22. package/crates/tish_compile_js/src/codegen.rs +50 -29
  23. package/crates/tish_compile_js/src/tests_jsx.rs +44 -0
  24. package/crates/tish_core/src/console_style.rs +9 -0
  25. package/crates/tish_core/src/json.rs +17 -7
  26. package/crates/tish_core/src/macros.rs +2 -2
  27. package/crates/tish_core/src/value.rs +192 -4
  28. package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
  29. package/crates/tish_eval/src/eval.rs +135 -73
  30. package/crates/tish_eval/src/http.rs +18 -12
  31. package/crates/tish_eval/src/lib.rs +29 -0
  32. package/crates/tish_eval/src/regex.rs +1 -1
  33. package/crates/tish_eval/src/value.rs +89 -4
  34. package/crates/tish_eval/src/value_convert.rs +30 -8
  35. package/crates/tish_fmt/src/lib.rs +4 -1
  36. package/crates/tish_lexer/src/lib.rs +7 -2
  37. package/crates/tish_llvm/src/lib.rs +2 -2
  38. package/crates/tish_lsp/src/builtin_goto.rs +111 -10
  39. package/crates/tish_lsp/src/import_goto.rs +35 -22
  40. package/crates/tish_lsp/src/main.rs +118 -85
  41. package/crates/tish_native/src/build.rs +187 -10
  42. package/crates/tish_native/src/lib.rs +92 -8
  43. package/crates/tish_parser/src/lib.rs +77 -0
  44. package/crates/tish_parser/src/parser.rs +71 -74
  45. package/crates/tish_pg/src/error.rs +1 -1
  46. package/crates/tish_pg/src/lib.rs +61 -73
  47. package/crates/tish_resolve/src/lib.rs +283 -158
  48. package/crates/tish_resolve/src/pos.rs +10 -2
  49. package/crates/tish_runtime/Cargo.toml +3 -0
  50. package/crates/tish_runtime/src/http.rs +39 -39
  51. package/crates/tish_runtime/src/http_fetch.rs +12 -12
  52. package/crates/tish_runtime/src/lib.rs +26 -43
  53. package/crates/tish_runtime/src/native_promise.rs +0 -11
  54. package/crates/tish_runtime/src/promise.rs +14 -1
  55. package/crates/tish_runtime/src/promise_io.rs +1 -4
  56. package/crates/tish_runtime/src/ws.rs +40 -27
  57. package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
  58. package/crates/tish_ui/src/jsx.rs +6 -4
  59. package/crates/tish_ui/src/lib.rs +2 -2
  60. package/crates/tish_ui/src/runtime/hooks.rs +5 -15
  61. package/crates/tish_ui/src/runtime/mod.rs +16 -17
  62. package/crates/tish_vm/Cargo.toml +2 -0
  63. package/crates/tish_vm/src/vm.rs +218 -153
  64. package/crates/tish_wasm/src/lib.rs +33 -7
  65. package/crates/tish_wasm_runtime/Cargo.toml +4 -1
  66. package/crates/tish_wasm_runtime/src/lib.rs +2 -1
  67. package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
  68. package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
  69. package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
  70. package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
  71. package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
  72. package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
  73. package/justfile +3 -3
  74. package/package.json +1 -1
  75. package/platform/darwin-arm64/tish +0 -0
  76. package/platform/darwin-x64/tish +0 -0
  77. package/platform/linux-arm64/tish +0 -0
  78. package/platform/linux-x64/tish +0 -0
  79. package/platform/win32-x64/tish.exe +0 -0
package/bin/tish CHANGED
Binary file
@@ -539,9 +539,10 @@ pub fn convert_params(
539
539
  let fp = p;
540
540
  {
541
541
  let (name, name_span) = match &fp.pattern {
542
- oxc::ast::ast::BindingPattern::BindingIdentifier(b) => {
543
- (b.name.as_str(), crate::span_util::oxc_span_to_tish(ctx.1, b.as_ref()))
544
- }
542
+ oxc::ast::ast::BindingPattern::BindingIdentifier(b) => (
543
+ b.name.as_str(),
544
+ crate::span_util::oxc_span_to_tish(ctx.1, b.as_ref()),
545
+ ),
545
546
  _ => {
546
547
  return Err(ConvertError::new(ConvertErrorKind::Unsupported {
547
548
  what: "destructuring in params".into(),
@@ -565,9 +566,10 @@ pub fn convert_params(
565
566
  if rest_param.is_none() {
566
567
  if let Some(rest) = &params.rest {
567
568
  let (rest_name, rest_name_span) = match &rest.rest.argument {
568
- oxc::ast::ast::BindingPattern::BindingIdentifier(b) => {
569
- (b.name.as_str(), crate::span_util::oxc_span_to_tish(ctx.1, b.as_ref()))
570
- }
569
+ oxc::ast::ast::BindingPattern::BindingIdentifier(b) => (
570
+ b.name.as_str(),
571
+ crate::span_util::oxc_span_to_tish(ctx.1, b.as_ref()),
572
+ ),
571
573
  _ => {
572
574
  return Err(ConvertError::new(ConvertErrorKind::Unsupported {
573
575
  what: "rest param with non-identifier".into(),
@@ -247,9 +247,10 @@ fn convert_for_of_statement(
247
247
  if v.declarations.len() == 1 {
248
248
  let d = &v.declarations[0];
249
249
  match &d.id {
250
- oxc::ast::ast::BindingPattern::BindingIdentifier(b) => {
251
- (b.name.as_str(), span_util::oxc_span_to_tish(ctx.1, b.as_ref()))
252
- }
250
+ oxc::ast::ast::BindingPattern::BindingIdentifier(b) => (
251
+ b.name.as_str(),
252
+ span_util::oxc_span_to_tish(ctx.1, b.as_ref()),
253
+ ),
253
254
  _ => {
254
255
  return Err(ConvertError::new(ConvertErrorKind::Incompatible {
255
256
  what: "for-of with destructuring".into(),
@@ -368,16 +369,14 @@ fn convert_function_decl(
368
369
  span: Span,
369
370
  ) -> Result<Statement, ConvertError> {
370
371
  let async_ = f.r#async;
371
- let name: Arc<str> = f
372
- .id
373
- .as_ref()
374
- .map(|id| Arc::from(id.name.as_str()))
375
- .unwrap_or_else(|| Arc::from(""));
376
- let name_span = f
377
- .id
378
- .as_ref()
379
- .map(|id| span_util::oxc_span_to_tish(ctx.1, id))
380
- .unwrap_or_else(span_util::stub_span);
372
+ let name: Arc<str> =
373
+ f.id.as_ref()
374
+ .map(|id| Arc::from(id.name.as_str()))
375
+ .unwrap_or_else(|| Arc::from(""));
376
+ let name_span =
377
+ f.id.as_ref()
378
+ .map(|id| span_util::oxc_span_to_tish(ctx.1, id))
379
+ .unwrap_or_else(span_util::stub_span);
381
380
  let (params, rest_param) = expr::convert_params(&f.params, ctx)?;
382
381
  let body = match &f.body {
383
382
  Some(fb) => {
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tishlang"
3
- version = "1.9.1"
3
+ version = "1.10.0"
4
4
  edition = "2021"
5
5
  description = "Tish CLI - run, REPL, compile to native"
6
6
  license-file = { workspace = true }
@@ -19,7 +19,10 @@ pub(crate) fn register_bytecode_native_modules(vm: &mut tishlang_vm::Vm) {
19
19
  Arc::from("query_prepared"),
20
20
  Value::native(tishlang_pg::query_prepared),
21
21
  );
22
- om.insert(Arc::from("query_all"), Value::native(tishlang_pg::query_all));
22
+ om.insert(
23
+ Arc::from("query_all"),
24
+ Value::native(tishlang_pg::query_all),
25
+ );
23
26
  om.insert(Arc::from("migrate"), Value::native(tishlang_pg::migrate));
24
27
  om.insert(Arc::from("close"), Value::native(tishlang_pg::close));
25
28
  vm.register_native_module("cargo:tish_pg", om);
@@ -4,13 +4,11 @@ mod cargo_native_registry;
4
4
  mod cli_help;
5
5
  mod repl_completion;
6
6
 
7
- use std::cell::RefCell;
8
- use tishlang_core::VmRef;
9
7
  use std::collections::HashSet;
10
8
  use std::fs;
11
9
  use std::io::{self, IsTerminal, Read, Write};
12
10
  use std::path::{Path, PathBuf};
13
- use std::rc::Rc;
11
+ use tishlang_core::VmRef;
14
12
 
15
13
  use clap::FromArgMatches;
16
14
  use rustyline::{Behavior, ColorMode, CompletionType, Config, Editor};
@@ -516,13 +514,14 @@ fn compile_to_js(
516
514
  } else {
517
515
  program
518
516
  };
519
- let js = tishlang_compile_js::compile_with_jsx(&p, optimize).map_err(|e| format!("{}", e))?;
517
+ let js =
518
+ tishlang_compile_js::compile_with_jsx(&p, optimize).map_err(|e| format!("{}", e))?;
520
519
  (js, None)
521
520
  } else if input_path.extension().map(|e| e == "js") == Some(true) {
522
521
  let source = fs::read_to_string(input_path).map_err(|e| format!("{}", e))?;
523
522
  let program = tishlang_js_to_tish::convert(&source).map_err(|e| format!("{}", e))?;
524
- let js =
525
- tishlang_compile_js::compile_with_jsx(&program, optimize).map_err(|e| format!("{}", e))?;
523
+ let js = tishlang_compile_js::compile_with_jsx(&program, optimize)
524
+ .map_err(|e| format!("{}", e))?;
526
525
  (js, None)
527
526
  } else if source_map {
528
527
  let bundle = tishlang_compile_js::compile_project_with_jsx_and_source_map(
@@ -545,7 +544,8 @@ fn compile_to_js(
545
544
  let mut js_out = js;
546
545
  if let Some(map) = &map_json {
547
546
  let map_path = out_path.with_extension("js.map");
548
- fs::write(&map_path, map).map_err(|e| format!("Cannot write {}: {}", map_path.display(), e))?;
547
+ fs::write(&map_path, map)
548
+ .map_err(|e| format!("Cannot write {}: {}", map_path.display(), e))?;
549
549
  let map_url = map_path
550
550
  .file_name()
551
551
  .and_then(|s| s.to_str())
@@ -553,7 +553,8 @@ fn compile_to_js(
553
553
  js_out.push_str(&format!("\n//# sourceMappingURL={map_url}\n"));
554
554
  println!("Built: {}", map_path.display());
555
555
  }
556
- fs::write(&out_path, js_out).map_err(|e| format!("Cannot write {}: {}", out_path.display(), e))?;
556
+ fs::write(&out_path, js_out)
557
+ .map_err(|e| format!("Cannot write {}: {}", out_path.display(), e))?;
557
558
  println!("Built: {}", out_path.display());
558
559
  Ok(())
559
560
  }
@@ -611,11 +612,13 @@ fn build_file(
611
612
  Some(p)
612
613
  }
613
614
  });
615
+ let features = native_build_features_from_cli(cli_features);
614
616
  return tishlang_wasm::compile_to_wasi(
615
617
  &input_path,
616
618
  project_root,
617
619
  Path::new(output_path),
618
620
  optimize,
621
+ &features,
619
622
  )
620
623
  .map_err(|e| e.to_string());
621
624
  }
@@ -5,6 +5,7 @@
5
5
  //! - Generate/update expected files: `REGENERATE_EXPECTED=1 cargo test -p tishlangtest_mvp_programs_interpreter`
6
6
  //! then commit the new/updated `tests/core/*.tish.expected` files.
7
7
  //! - Compiled outputs are cached under `target/integration_compile_cache/` per backend.
8
+ //! MVP native tests use `native_many/<hash>/` plus one batched nested Cargo build.
8
9
 
9
10
  use std::collections::hash_map::DefaultHasher;
10
11
  use std::ffi::OsString;
@@ -14,6 +15,7 @@ use std::path::{Path, PathBuf};
14
15
  use std::process::Command;
15
16
 
16
17
  use rayon::prelude::*;
18
+ use tishlang_native::compile_many_to_native;
17
19
 
18
20
  fn workspace_root() -> PathBuf {
19
21
  PathBuf::from(env!("CARGO_MANIFEST_DIR"))
@@ -50,6 +52,72 @@ fn integration_compile_cache_dir() -> PathBuf {
50
52
  target_dir().join("integration_compile_cache")
51
53
  }
52
54
 
55
+ /// Match `tish build` with no `--feature`: link every capability compiled into this `tish` binary.
56
+ fn native_build_features_for_integration_test() -> Vec<String> {
57
+ let mut v: Vec<String> = tishlang_vm::all_compiled_capabilities()
58
+ .into_iter()
59
+ .collect();
60
+ v.sort();
61
+ v
62
+ }
63
+
64
+ fn combined_mvp_native_inputs_hash(paths: &[PathBuf]) -> u64 {
65
+ let mut h = DefaultHasher::new();
66
+ let feats = native_build_features_for_integration_test();
67
+ feats.len().hash(&mut h);
68
+ for f in &feats {
69
+ f.hash(&mut h);
70
+ }
71
+ paths.len().hash(&mut h);
72
+ for p in paths {
73
+ p.file_name()
74
+ .unwrap_or_default()
75
+ .to_string_lossy()
76
+ .hash(&mut h);
77
+ file_content_hash(p).hash(&mut h);
78
+ }
79
+ // Native batch cache must invalidate when the emitter or `value_call` changes — not only
80
+ // when `.tish` sources change; otherwise CI/rust-cache can keep stale nested binaries.
81
+ let codegen_rs = workspace_root().join("crates/tish_compile/src/codegen.rs");
82
+ if codegen_rs.is_file() {
83
+ file_content_hash(&codegen_rs).hash(&mut h);
84
+ }
85
+ let value_rs = workspace_root().join("crates/tish_core/src/value.rs");
86
+ if value_rs.is_file() {
87
+ file_content_hash(&value_rs).hash(&mut h);
88
+ }
89
+ h.finish()
90
+ }
91
+
92
+ fn mvp_native_batch_cache_dir(combined: u64) -> PathBuf {
93
+ integration_compile_cache_dir()
94
+ .join("native_many")
95
+ .join(format!("{:016x}", combined))
96
+ }
97
+
98
+ /// Restores the previous process env when dropped (for `TISH_FAST_NATIVE_BUILD` in batch tests).
99
+ struct EnvVarGuard {
100
+ key: &'static str,
101
+ previous: Option<std::ffi::OsString>,
102
+ }
103
+
104
+ impl EnvVarGuard {
105
+ fn set(key: &'static str, value: &str) -> Self {
106
+ let previous = std::env::var_os(key);
107
+ std::env::set_var(key, value);
108
+ Self { key, previous }
109
+ }
110
+ }
111
+
112
+ impl Drop for EnvVarGuard {
113
+ fn drop(&mut self) {
114
+ match &self.previous {
115
+ None => std::env::remove_var(self.key),
116
+ Some(v) => std::env::set_var(self.key, v),
117
+ }
118
+ }
119
+ }
120
+
53
121
  fn file_content_hash(path: &Path) -> u64 {
54
122
  let mut f = std::fs::File::open(path).expect("open file for hash");
55
123
  let mut content = Vec::new();
@@ -583,6 +651,7 @@ const MVP_TEST_FILES: &[&str] = &[
583
651
  "break_continue.tish",
584
652
  "length.tish",
585
653
  "objects.tish",
654
+ "symbol.tish",
586
655
  "conditional.tish",
587
656
  "switch.tish",
588
657
  "do_while.tish",
@@ -714,6 +783,7 @@ fn test_mvp_programs_interp_vm_stdout_parity() {
714
783
  /// Compile each .tish file to native, run, and compare stdout to static expected (parallelized).
715
784
  #[test]
716
785
  fn test_mvp_programs_native() {
786
+ let _fast_native = EnvVarGuard::set("TISH_FAST_NATIVE_BUILD", "1");
717
787
  let core_dir = core_dir();
718
788
  let bin = tish_bin();
719
789
  assert!(
@@ -721,18 +791,86 @@ fn test_mvp_programs_native() {
721
791
  "tish binary not found at {}. Run `cargo build -p tishlang` first.",
722
792
  bin.display()
723
793
  );
724
- let errors: Vec<String> = MVP_TEST_FILES
725
- .par_iter()
794
+
795
+ let mut paths: Vec<PathBuf> = MVP_TEST_FILES
796
+ .iter()
726
797
  .filter_map(|name| {
727
- let path = core_dir.join(name);
728
- if !path.exists() {
729
- return None;
798
+ let p = core_dir.join(name);
799
+ if p.exists() {
800
+ Some(p)
801
+ } else {
802
+ None
730
803
  }
731
- let expected = match get_expected(&path) {
804
+ })
805
+ .collect();
806
+ paths.sort();
807
+
808
+ if paths.is_empty() {
809
+ return;
810
+ }
811
+
812
+ let combined = combined_mvp_native_inputs_hash(&paths);
813
+ let cache_dir = mvp_native_batch_cache_dir(combined);
814
+ let _ = std::fs::create_dir_all(&cache_dir);
815
+
816
+ let ext = if cfg!(target_os = "windows") {
817
+ ".exe"
818
+ } else {
819
+ ""
820
+ };
821
+
822
+ let entries_owned: Vec<(PathBuf, PathBuf)> = paths
823
+ .iter()
824
+ .map(|p| {
825
+ let stem = p.file_stem().unwrap().to_string_lossy();
826
+ let cached = cache_dir.join(format!("{}{}", stem, ext));
827
+ (p.clone(), cached)
828
+ })
829
+ .collect();
830
+
831
+ let need_build = entries_owned.iter().any(|(_, o)| !o.exists());
832
+ if need_build {
833
+ let refs: Vec<(&Path, &Path)> = entries_owned
834
+ .iter()
835
+ .map(|(a, b)| (a.as_path(), b.as_path()))
836
+ .collect();
837
+ let feats = native_build_features_for_integration_test();
838
+ compile_many_to_native(&refs, Some(workspace_root().as_path()), &feats, true)
839
+ .unwrap_or_else(|e| panic!("compile_many_to_native: {}", e.message));
840
+ }
841
+
842
+ // Run each binary sequentially. Parallel `fs::copy` + `exec` caused Linux ETXTBSY (errno 26)
843
+ // in CI when several threads replaced/ran temp executables under load.
844
+ let errors: Vec<String> = entries_owned
845
+ .iter()
846
+ .enumerate()
847
+ .filter_map(|(run_index, (path, cached_bin))| {
848
+ let expected = match get_expected(path) {
732
849
  Some(e) => e,
733
850
  None => return Some(format!("missing expected: {}", path.display())),
734
851
  };
735
- let out_bin = compile_cached(&bin, &path, "native");
852
+ if !cached_bin.exists() {
853
+ return Some(format!("missing cached binary: {}", cached_bin.display()));
854
+ }
855
+ let stem = path.file_stem().unwrap().to_string_lossy();
856
+ let ext_bin = cached_bin
857
+ .extension()
858
+ .map(|e| e.to_string_lossy().to_string())
859
+ .unwrap_or_default();
860
+ let temp_dest = std::env::temp_dir().join(format!(
861
+ "tish_mvp_native_{}_{:x}_{}_{}",
862
+ stem,
863
+ file_content_hash(path),
864
+ std::process::id(),
865
+ run_index
866
+ ));
867
+ let temp_dest = if ext_bin.is_empty() {
868
+ temp_dest
869
+ } else {
870
+ temp_dest.with_extension(&ext_bin)
871
+ };
872
+ std::fs::copy(cached_bin, &temp_dest).expect("copy cached native bin to temp");
873
+ let out_bin = temp_dest;
736
874
  let out = match Command::new(&out_bin)
737
875
  .current_dir(workspace_root())
738
876
  .output()
@@ -129,15 +129,9 @@ pub enum ImportSpecifier {
129
129
  alias_span: Option<Span>,
130
130
  },
131
131
  /// Namespace: * as M
132
- Namespace {
133
- name: Arc<str>,
134
- name_span: Span,
135
- },
132
+ Namespace { name: Arc<str>, name_span: Span },
136
133
  /// Default: import X from "..."
137
- Default {
138
- name: Arc<str>,
139
- name_span: Span,
140
- },
134
+ Default { name: Arc<str>, name_span: Span },
141
135
  }
142
136
 
143
137
  /// Export declaration: named (const/let/fn) or default
@@ -616,7 +610,7 @@ impl Statement {
616
610
  | Statement::DoWhile { span, .. }
617
611
  | Statement::Throw { span, .. }
618
612
  | Statement::Try { span, .. }
619
- | Statement::Import { span, .. }
613
+ | Statement::Import { span, .. }
620
614
  | Statement::Export { span, .. }
621
615
  | Statement::TypeAlias { span, .. }
622
616
  | Statement::DeclareVar { span, .. }
@@ -6,6 +6,18 @@
6
6
  use std::fs;
7
7
  use std::path::{Path, PathBuf};
8
8
  use std::process::Command;
9
+ use std::sync::Mutex;
10
+
11
+ /// Serialize nested `cargo build` calls that share the workspace `target/` dir (tests + `tish build`).
12
+ static NESTED_CARGO_MUTEX: Mutex<()> = Mutex::new(());
13
+
14
+ fn mold_available() -> bool {
15
+ Command::new("mold")
16
+ .arg("--version")
17
+ .output()
18
+ .map(|o| o.status.success())
19
+ .unwrap_or(false)
20
+ }
9
21
 
10
22
  /// True if `root` looks like the Tish language repo (has `crates/tish_runtime`).
11
23
  ///
@@ -161,7 +173,11 @@ pub fn find_workspace_root() -> Result<PathBuf, String> {
161
173
  let candidate = dir.join("tish");
162
174
  if is_tish_workspace_root(&candidate) {
163
175
  return candidate.canonicalize().map_err(|e| {
164
- format!("Cannot canonicalize Tish workspace {}: {}", candidate.display(), e)
176
+ format!(
177
+ "Cannot canonicalize Tish workspace {}: {}",
178
+ candidate.display(),
179
+ e
180
+ )
165
181
  });
166
182
  }
167
183
  if !dir.pop() {
@@ -366,20 +382,31 @@ fn protoc_for_nested_cargo() -> Option<PathBuf> {
366
382
  /// Run cargo build in the given directory.
367
383
  /// If target_dir is Some, use that for --target-dir (e.g. workspace target for caching).
368
384
  pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<(), String> {
385
+ let _nested_guard = NESTED_CARGO_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
386
+
369
387
  let target_dir = target_dir
370
388
  .map(|p| p.to_path_buf())
371
389
  .unwrap_or_else(|| build_dir.join("target"));
390
+ let fast_native = std::env::var("TISH_FAST_NATIVE_BUILD").as_deref() == Ok("1");
391
+
372
392
  // Default to target-cpu=native so the emitted binary uses every SIMD / ISA
373
393
  // extension the build host supports. Callers can override by pre-setting
374
- // RUSTFLAGS in the environment.
375
- let existing_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
376
- let merged_rustflags = if existing_rustflags.is_empty() {
377
- "-C target-cpu=native".to_string()
378
- } else if existing_rustflags.contains("target-cpu") {
379
- existing_rustflags
380
- } else {
381
- format!("{} -C target-cpu=native", existing_rustflags)
382
- };
394
+ // RUSTFLAGS in the environment. Skipped for fast nested builds (integration tests).
395
+ let mut merged_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
396
+ if fast_native {
397
+ if cfg!(target_os = "linux")
398
+ && mold_available()
399
+ && !merged_rustflags.contains("fuse-ld=mold")
400
+ {
401
+ merged_rustflags = format!("{} -C link-arg=-fuse-ld=mold", merged_rustflags.trim());
402
+ merged_rustflags = merged_rustflags.trim().to_string();
403
+ }
404
+ } else if merged_rustflags.is_empty() {
405
+ merged_rustflags = "-C target-cpu=native".to_string();
406
+ } else if !merged_rustflags.contains("target-cpu") {
407
+ merged_rustflags = format!("{} -C target-cpu=native", merged_rustflags);
408
+ }
409
+
383
410
  // Nested `cargo build` (e.g. `tish build --native-backend rust`) inherits the parent
384
411
  // environment. CI often sets `RUSTC_WRAPPER=sccache`; wrapping this inner compile too can
385
412
  // cause flaky or failed builds (LTO / temp-crate paths). Use plain rustc here; the main
@@ -394,6 +421,11 @@ pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<()
394
421
  .env_remove("CARGO_BUILD_RUSTC_WRAPPER")
395
422
  .env("CARGO_TERM_PROGRESS", "always")
396
423
  .env("RUSTFLAGS", &merged_rustflags);
424
+ if fast_native {
425
+ cmd.env("CARGO_INCREMENTAL", "1");
426
+ } else {
427
+ cmd.env_remove("CARGO_INCREMENTAL");
428
+ }
397
429
  if let Some(protoc) = protoc_for_nested_cargo() {
398
430
  cmd.env("PROTOC", protoc);
399
431
  }
@@ -422,11 +454,7 @@ mod protoc_tests {
422
454
  let _guard = _lock.lock().unwrap();
423
455
  std::env::remove_var("PROTOC");
424
456
  let p = protoc_for_nested_cargo().expect("expected vendored or PATH protoc");
425
- assert!(
426
- p.exists(),
427
- "resolved protoc should exist: {}",
428
- p.display()
429
- );
457
+ assert!(p.exists(), "resolved protoc should exist: {}", p.display());
430
458
  }
431
459
  }
432
460
 
@@ -1,10 +1,8 @@
1
1
  //! Array builtin methods.
2
2
 
3
3
  use crate::helpers::normalize_index;
4
- use tishlang_core::VmRef;
5
- use std::cell::RefCell;
6
- use std::rc::Rc;
7
4
  use tishlang_core::Value;
5
+ use tishlang_core::VmRef;
8
6
 
9
7
  /// Create a new array Value from a Vec of Values.
10
8
  pub fn from_vec(v: Vec<Value>) -> Value {
@@ -434,6 +432,7 @@ fn get_prop_number(v: &Value, prop: &std::sync::Arc<str>) -> f64 {
434
432
  match v {
435
433
  Value::Object(o) => o
436
434
  .borrow()
435
+ .strings
437
436
  .get(prop.as_ref())
438
437
  .map(|v| v.as_number().unwrap_or(f64::NAN))
439
438
  .unwrap_or(f64::NAN),
@@ -1,12 +1,8 @@
1
1
  //! `new` lowering for non-JS targets: `construct(callee, args)` approximates JS `[[Construct]]`.
2
2
  //! Browser-exact behavior remains on `tish build --target js`.
3
3
 
4
- use std::cell::RefCell;
5
- use tishlang_core::VmRef;
6
- use std::rc::Rc;
7
4
  use std::sync::Arc;
8
-
9
- use tishlang_core::{ObjectMap, Value};
5
+ use tishlang_core::{ObjectMap, Value, VmRef};
10
6
 
11
7
  const CONSTRUCT: &str = "__construct";
12
8
 
@@ -16,7 +12,7 @@ pub fn construct(callee: &Value, args: &[Value]) -> Value {
16
12
  Value::Function(f) => f(args),
17
13
  Value::Object(o) => {
18
14
  let b = o.borrow();
19
- if let Some(Value::Function(ctor)) = b.get(&Arc::from(CONSTRUCT)) {
15
+ if let Some(Value::Function(ctor)) = b.strings.get(&Arc::from(CONSTRUCT)) {
20
16
  let c = ctor.clone();
21
17
  drop(b);
22
18
  return c(args);
@@ -30,7 +26,7 @@ pub fn construct(callee: &Value, args: &[Value]) -> Value {
30
26
  fn param(initial: f64) -> Value {
31
27
  let mut m = ObjectMap::default();
32
28
  m.insert(Arc::from("value"), Value::Number(initial));
33
- Value::Object(VmRef::new(m))
29
+ Value::object(m)
34
30
  }
35
31
 
36
32
  fn connect_fn() -> Value {
@@ -45,21 +41,21 @@ fn audio_node_stub() -> Value {
45
41
  m.insert(Arc::from("frequency"), param(440.0));
46
42
  m.insert(Arc::from("Q"), param(1.0));
47
43
  m.insert(Arc::from("type"), Value::String("peaking".into()));
48
- Value::Object(VmRef::new(m))
44
+ Value::object(m)
49
45
  }
50
46
 
51
47
  fn analyser_stub() -> Value {
52
48
  let mut m = ObjectMap::default();
53
49
  m.insert(Arc::from("connect"), connect_fn());
54
50
  m.insert(Arc::from("fftSize"), Value::Number(2048.0));
55
- Value::Object(VmRef::new(m))
51
+ Value::object(m)
56
52
  }
57
53
 
58
54
  fn stereo_panner_stub() -> Value {
59
55
  let mut m = ObjectMap::default();
60
56
  m.insert(Arc::from("connect"), connect_fn());
61
57
  m.insert(Arc::from("pan"), param(0.0));
62
- Value::Object(VmRef::new(m))
58
+ Value::object(m)
63
59
  }
64
60
 
65
61
  fn audio_buffer_stub(len: usize) -> Value {
@@ -71,7 +67,7 @@ fn audio_buffer_stub(len: usize) -> Value {
71
67
  Arc::from("getChannelData"),
72
68
  Value::native(move |_args| Value::Array(data2.clone())),
73
69
  );
74
- Value::Object(VmRef::new(m))
70
+ Value::object(m)
75
71
  }
76
72
 
77
73
  fn buffer_source_stub() -> Value {
@@ -79,12 +75,9 @@ fn buffer_source_stub() -> Value {
79
75
  m.insert(Arc::from("buffer"), Value::Null);
80
76
  m.insert(Arc::from("loop"), Value::Bool(false));
81
77
  m.insert(Arc::from("connect"), connect_fn());
82
- m.insert(
83
- Arc::from("start"),
84
- Value::native(|_| Value::Null),
85
- );
78
+ m.insert(Arc::from("start"), Value::native(|_| Value::Null));
86
79
  m.insert(Arc::from("stop"), Value::native(|_| Value::Null));
87
- Value::Object(VmRef::new(m))
80
+ Value::object(m)
88
81
  }
89
82
 
90
83
  fn oscillator_stub() -> Value {
@@ -92,12 +85,9 @@ fn oscillator_stub() -> Value {
92
85
  m.insert(Arc::from("frequency"), param(440.0));
93
86
  m.insert(Arc::from("type"), Value::String("sine".into()));
94
87
  m.insert(Arc::from("connect"), connect_fn());
95
- m.insert(
96
- Arc::from("start"),
97
- Value::native(|_| Value::Null),
98
- );
88
+ m.insert(Arc::from("start"), Value::native(|_| Value::Null));
99
89
  m.insert(Arc::from("stop"), Value::native(|_| Value::Null));
100
- Value::Object(VmRef::new(m))
90
+ Value::object(m)
101
91
  }
102
92
 
103
93
  fn audio_context_instance() -> Value {
@@ -140,12 +130,9 @@ fn audio_context_instance() -> Value {
140
130
  Arc::from("createOscillator"),
141
131
  Value::native(|_| oscillator_stub()),
142
132
  );
143
- ctx.insert(
144
- Arc::from("decodeAudioData"),
145
- Value::native(|_| Value::Null),
146
- );
133
+ ctx.insert(Arc::from("decodeAudioData"), Value::native(|_| Value::Null));
147
134
 
148
- Value::Object(VmRef::new(ctx))
135
+ Value::object(ctx)
149
136
  }
150
137
 
151
138
  /// Global `Uint8Array` for native/VM: `new Uint8Array(n)` → numeric array of zeros (not real bytes).
@@ -160,7 +147,7 @@ pub fn uint8_array_constructor_value() -> Value {
160
147
  });
161
148
  let mut m = ObjectMap::default();
162
149
  m.insert(Arc::from(CONSTRUCT), ctor);
163
- Value::Object(VmRef::new(m))
150
+ Value::object(m)
164
151
  }
165
152
 
166
153
  /// Global `AudioContext` for native/VM: stub graph (no real audio).
@@ -168,5 +155,5 @@ pub fn audio_context_constructor_value() -> Value {
168
155
  let ctor = Value::native(|_args: &[Value]| audio_context_instance());
169
156
  let mut m = ObjectMap::default();
170
157
  m.insert(Arc::from(CONSTRUCT), ctor);
171
- Value::Object(VmRef::new(m))
158
+ Value::object(m)
172
159
  }