@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
@@ -30,11 +30,11 @@ pub enum TypeAnnotation {
30
30
  #[derive(Debug, Clone, PartialEq)]
31
31
  pub struct TypedParam {
32
32
  pub name: Arc<str>,
33
+ pub name_span: Span,
33
34
  pub type_ann: Option<TypeAnnotation>,
34
35
  pub default: Option<Expr>,
35
36
  }
36
37
 
37
-
38
38
  /// Single formal parameter: simple identifier or destructuring pattern.
39
39
  #[derive(Debug, Clone, PartialEq)]
40
40
  pub enum FunParam {
@@ -46,7 +46,6 @@ pub enum FunParam {
46
46
  },
47
47
  }
48
48
 
49
-
50
49
  impl FunParam {
51
50
  /// Variable names introduced by this formal parameter.
52
51
  pub fn bound_names(&self) -> Vec<Arc<str>> {
@@ -66,11 +65,11 @@ impl FunParam {
66
65
  for el in elements {
67
66
  if let Some(el) = el {
68
67
  match el {
69
- DestructElement::Ident(n) => out.push(Arc::clone(n)),
68
+ DestructElement::Ident(n, _) => out.push(Arc::clone(n)),
70
69
  DestructElement::Pattern(p) => {
71
70
  Self::collect_pattern_binding_names(p, out);
72
71
  }
73
- DestructElement::Rest(n) => out.push(Arc::clone(n)),
72
+ DestructElement::Rest(n, _) => out.push(Arc::clone(n)),
74
73
  }
75
74
  }
76
75
  }
@@ -78,11 +77,11 @@ impl FunParam {
78
77
  DestructPattern::Object(props) => {
79
78
  for prop in props {
80
79
  match &prop.value {
81
- DestructElement::Ident(n) => out.push(Arc::clone(n)),
80
+ DestructElement::Ident(n, _) => out.push(Arc::clone(n)),
82
81
  DestructElement::Pattern(p) => {
83
82
  Self::collect_pattern_binding_names(p, out);
84
83
  }
85
- DestructElement::Rest(n) => out.push(Arc::clone(n)),
84
+ DestructElement::Rest(n, _) => out.push(Arc::clone(n)),
86
85
  }
87
86
  }
88
87
  }
@@ -103,11 +102,11 @@ pub enum DestructPattern {
103
102
  #[derive(Debug, Clone, PartialEq)]
104
103
  pub enum DestructElement {
105
104
  /// Simple binding: a
106
- Ident(Arc<str>),
105
+ Ident(Arc<str>, Span),
107
106
  /// Nested pattern: [a, b] or { x, y }
108
107
  Pattern(Box<DestructPattern>),
109
108
  /// Rest element: ...rest
110
- Rest(Arc<str>),
109
+ Rest(Arc<str>, Span),
111
110
  }
112
111
 
113
112
  /// Property in object destructuring pattern
@@ -123,11 +122,22 @@ pub struct DestructProp {
123
122
  #[derive(Debug, Clone, PartialEq)]
124
123
  pub enum ImportSpecifier {
125
124
  /// Named: { foo } or { foo as bar }
126
- Named { name: Arc<str>, alias: Option<Arc<str>> },
125
+ Named {
126
+ name: Arc<str>,
127
+ name_span: Span,
128
+ alias: Option<Arc<str>>,
129
+ alias_span: Option<Span>,
130
+ },
127
131
  /// Namespace: * as M
128
- Namespace(Arc<str>),
132
+ Namespace {
133
+ name: Arc<str>,
134
+ name_span: Span,
135
+ },
129
136
  /// Default: import X from "..."
130
- Default(Arc<str>),
137
+ Default {
138
+ name: Arc<str>,
139
+ name_span: Span,
140
+ },
131
141
  }
132
142
 
133
143
  /// Export declaration: named (const/let/fn) or default
@@ -152,6 +162,7 @@ pub enum Statement {
152
162
  },
153
163
  VarDecl {
154
164
  name: Arc<str>,
165
+ name_span: Span,
155
166
  mutable: bool, // true for `let`, false for `const`
156
167
  type_ann: Option<TypeAnnotation>,
157
168
  init: Option<Expr>,
@@ -188,6 +199,7 @@ pub enum Statement {
188
199
  },
189
200
  ForOf {
190
201
  name: Arc<str>,
202
+ name_span: Span,
191
203
  iterable: Expr,
192
204
  body: Box<Statement>,
193
205
  span: Span,
@@ -205,6 +217,7 @@ pub enum Statement {
205
217
  FunDecl {
206
218
  async_: bool,
207
219
  name: Arc<str>,
220
+ name_span: Span,
208
221
  params: Vec<FunParam>,
209
222
  rest_param: Option<TypedParam>,
210
223
  return_type: Option<TypeAnnotation>,
@@ -229,6 +242,7 @@ pub enum Statement {
229
242
  Try {
230
243
  body: Box<Statement>,
231
244
  catch_param: Option<Arc<str>>,
245
+ catch_param_span: Option<Span>,
232
246
  catch_body: Option<Box<Statement>>,
233
247
  finally_body: Option<Box<Statement>>,
234
248
  span: Span,
@@ -242,6 +256,31 @@ pub enum Statement {
242
256
  declaration: Box<ExportDeclaration>,
243
257
  span: Span,
244
258
  },
259
+ /// `type Name = Type` (erased at runtime; for checker / declaration files).
260
+ TypeAlias {
261
+ name: Arc<str>,
262
+ name_span: Span,
263
+ ty: TypeAnnotation,
264
+ span: Span,
265
+ },
266
+ /// `declare let name: T` or `declare const name: T`
267
+ DeclareVar {
268
+ name: Arc<str>,
269
+ name_span: Span,
270
+ type_ann: Option<TypeAnnotation>,
271
+ const_: bool,
272
+ span: Span,
273
+ },
274
+ /// `declare [async] function name(...): R` (no body).
275
+ DeclareFun {
276
+ async_: bool,
277
+ name: Arc<str>,
278
+ name_span: Span,
279
+ params: Vec<FunParam>,
280
+ rest_param: Option<TypedParam>,
281
+ return_type: Option<TypeAnnotation>,
282
+ span: Span,
283
+ },
245
284
  }
246
285
 
247
286
  #[derive(Debug, Clone, PartialEq)]
@@ -366,8 +405,8 @@ pub enum Expr {
366
405
  },
367
406
  /// Template literal: `text ${expr} text`
368
407
  TemplateLiteral {
369
- quasis: Vec<Arc<str>>, // Static string parts (n+1 for n expressions)
370
- exprs: Vec<Expr>, // Interpolated expressions (n)
408
+ quasis: Vec<Arc<str>>, // Static string parts (n+1 for n expressions)
409
+ exprs: Vec<Expr>, // Interpolated expressions (n)
371
410
  span: Span,
372
411
  },
373
412
  /// Await expression: await operand
@@ -399,10 +438,7 @@ pub enum Expr {
399
438
  #[derive(Debug, Clone, PartialEq)]
400
439
  pub enum JsxProp {
401
440
  /// name="value" or name={expr} or name (boolean shorthand)
402
- Attr {
403
- name: Arc<str>,
404
- value: JsxAttrValue,
405
- },
441
+ Attr { name: Arc<str>, value: JsxAttrValue },
406
442
  /// {...expr}
407
443
  Spread(Expr),
408
444
  }
@@ -493,11 +529,11 @@ pub enum CallArg {
493
529
 
494
530
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
495
531
  pub enum CompoundOp {
496
- Add, // +=
497
- Sub, // -=
498
- Mul, // *=
499
- Div, // /=
500
- Mod, // %=
532
+ Add, // +=
533
+ Sub, // -=
534
+ Mul, // *=
535
+ Div, // /=
536
+ Mod, // %=
501
537
  }
502
538
 
503
539
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -552,6 +588,39 @@ pub enum UnaryOp {
552
588
 
553
589
  #[derive(Debug, Clone, PartialEq)]
554
590
  pub enum MemberProp {
555
- Name(Arc<str>),
591
+ /// Property name in `obj.prop` / `obj?.prop` (span covers **prop** only).
592
+ Name {
593
+ name: Arc<str>,
594
+ span: Span,
595
+ },
556
596
  Expr(Box<Expr>), // for computed property
557
597
  }
598
+
599
+ impl Statement {
600
+ /// Source span covering this statement (including nested bodies where applicable).
601
+ pub fn span(&self) -> Span {
602
+ match self {
603
+ Statement::Block { span, .. }
604
+ | Statement::VarDecl { span, .. }
605
+ | Statement::VarDeclDestructure { span, .. }
606
+ | Statement::ExprStmt { span, .. }
607
+ | Statement::If { span, .. }
608
+ | Statement::While { span, .. }
609
+ | Statement::For { span, .. }
610
+ | Statement::ForOf { span, .. }
611
+ | Statement::Return { span, .. }
612
+ | Statement::Break { span, .. }
613
+ | Statement::Continue { span, .. }
614
+ | Statement::FunDecl { span, .. }
615
+ | Statement::Switch { span, .. }
616
+ | Statement::DoWhile { span, .. }
617
+ | Statement::Throw { span, .. }
618
+ | Statement::Try { span, .. }
619
+ | Statement::Import { span, .. }
620
+ | Statement::Export { span, .. }
621
+ | Statement::TypeAlias { span, .. }
622
+ | Statement::DeclareVar { span, .. }
623
+ | Statement::DeclareFun { span, .. } => *span,
624
+ }
625
+ }
626
+ }
@@ -5,3 +5,7 @@ edition = "2021"
5
5
  description = "Shared build utilities for Tish (workspace discovery, path resolution)"
6
6
  license-file = { workspace = true }
7
7
  repository = { workspace = true }
8
+
9
+ [dependencies]
10
+ # Bundled `protoc` for nested `cargo build` (Lance / prost-build) when none is on PATH.
11
+ protoc-bin-vendored = "3"
@@ -137,6 +137,39 @@ fn tish_root_from_project_cargo_files(mut start: PathBuf) -> Option<PathBuf> {
137
137
  /// Returns the directory containing the workspace Cargo.toml (with [workspace]).
138
138
  /// Used when building native binaries, WASM, or locating runtime crates.
139
139
  pub fn find_workspace_root() -> Result<PathBuf, String> {
140
+ // Strategy 0: explicit checkout (e.g. tish-hub `npm run dev` / native build from a JS-only tree)
141
+ if let Ok(root) = std::env::var("TISHLANG_WORKSPACE") {
142
+ let trimmed = root.trim();
143
+ if !trimmed.is_empty() {
144
+ let p = PathBuf::from(trimmed);
145
+ if p.is_dir() && is_tish_workspace_root(&p) {
146
+ return p.canonicalize().map_err(|e| {
147
+ format!(
148
+ "TISHLANG_WORKSPACE is set but not a usable directory: {}",
149
+ e
150
+ )
151
+ });
152
+ }
153
+ // Non-empty but invalid: fall through (sibling `tish/` discovery may still apply).
154
+ }
155
+ }
156
+
157
+ // Strategy 0b: monorepo layout `…/<parent>/tish-hub` (cwd) next to `…/<parent>/tish` (language repo).
158
+ // Works without `TISHLANG_WORKSPACE` so older `tish` binaries still find the compiler tree.
159
+ if let Ok(mut dir) = std::env::current_dir() {
160
+ for _ in 0..24 {
161
+ let candidate = dir.join("tish");
162
+ if is_tish_workspace_root(&candidate) {
163
+ return candidate.canonicalize().map_err(|e| {
164
+ format!("Cannot canonicalize Tish workspace {}: {}", candidate.display(), e)
165
+ });
166
+ }
167
+ if !dir.pop() {
168
+ break;
169
+ }
170
+ }
171
+ }
172
+
140
173
  // Strategy 1: CARGO_MANIFEST_DIR (works during cargo build/run from workspace)
141
174
  if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
142
175
  let path = PathBuf::from(&manifest_dir);
@@ -158,7 +191,9 @@ pub fn find_workspace_root() -> Result<PathBuf, String> {
158
191
  if let Some(mut current) = exe.parent() {
159
192
  for _ in 0..15 {
160
193
  let crates_dir = current.join("crates");
161
- if crates_dir.join("tish_runtime").exists() || crates_dir.join("tish_cranelift_runtime").exists() {
194
+ if crates_dir.join("tish_runtime").exists()
195
+ || crates_dir.join("tish_cranelift_runtime").exists()
196
+ {
162
197
  return Ok(current.to_path_buf());
163
198
  }
164
199
  if let Some(p) = current.parent() {
@@ -177,6 +212,19 @@ pub fn find_workspace_root() -> Result<PathBuf, String> {
177
212
  }
178
213
  }
179
214
 
215
+ // Strategy 3b: `node_modules/@tishlang/tish` from cwd or any ancestor (package.json-only apps)
216
+ if let Ok(mut dir) = std::env::current_dir() {
217
+ for _ in 0..32 {
218
+ let npm_pkg = dir.join("node_modules").join("@tishlang").join("tish");
219
+ if is_tish_workspace_root(&npm_pkg) {
220
+ return Ok(npm_pkg);
221
+ }
222
+ if !dir.pop() {
223
+ break;
224
+ }
225
+ }
226
+ }
227
+
180
228
  // Strategy 4: Walk from current working directory
181
229
  if let Ok(mut current) = std::env::current_dir() {
182
230
  for _ in 0..15 {
@@ -276,33 +324,112 @@ pub fn find_crate_path(crate_name: &str) -> Result<PathBuf, String> {
276
324
 
277
325
  /// Create a temp build directory with src subdir.
278
326
  pub fn create_build_dir(prefix: &str, out_name: &str) -> Result<PathBuf, String> {
279
- let build_dir = std::env::temp_dir().join(prefix).join(format!("{}_{}", out_name, std::process::id()));
327
+ let build_dir =
328
+ std::env::temp_dir()
329
+ .join(prefix)
330
+ .join(format!("{}_{}", out_name, std::process::id()));
280
331
  fs::create_dir_all(&build_dir).map_err(|e| format!("Cannot create build dir: {}", e))?;
281
- fs::create_dir_all(build_dir.join("src")).map_err(|e| format!("Cannot create src dir: {}", e))?;
332
+ fs::create_dir_all(build_dir.join("src"))
333
+ .map_err(|e| format!("Cannot create src dir: {}", e))?;
282
334
  Ok(build_dir)
283
335
  }
284
336
 
337
+ /// `PROTOC` for prost-build in transitive crates (e.g. lance-encoding) during nested `cargo build`.
338
+ /// Respects an existing `PROTOC`, then `protoc` on `PATH`, then `protoc-bin-vendored`.
339
+ fn protoc_for_nested_cargo() -> Option<PathBuf> {
340
+ // Empty or non-file PROTOC must not short-circuit: prost-build treats "" like a missing binary.
341
+ if let Some(v) = std::env::var_os("PROTOC") {
342
+ if !v.is_empty() {
343
+ let p = PathBuf::from(&v);
344
+ if p.is_file() {
345
+ return None;
346
+ }
347
+ }
348
+ }
349
+ // Prefer vendored protoc over PATH: a broken or non-executable `protoc` on PATH still passes `is_file()`.
350
+ if let Ok(p) = protoc_bin_vendored::protoc_bin_path() {
351
+ return Some(p);
352
+ }
353
+ let ext = if cfg!(windows) { ".exe" } else { "" };
354
+ let name = format!("protoc{}", ext);
355
+ if let Some(paths) = std::env::var_os("PATH") {
356
+ for dir in std::env::split_paths(&paths) {
357
+ let candidate = dir.join(&name);
358
+ if candidate.is_file() {
359
+ return Some(candidate);
360
+ }
361
+ }
362
+ }
363
+ None
364
+ }
365
+
285
366
  /// Run cargo build in the given directory.
286
367
  /// If target_dir is Some, use that for --target-dir (e.g. workspace target for caching).
287
368
  pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<(), String> {
288
- let target_dir = target_dir.map(|p| p.to_path_buf()).unwrap_or_else(|| build_dir.join("target"));
289
- let output = Command::new("cargo")
290
- .args(["build", "--release", "--target-dir"])
369
+ let target_dir = target_dir
370
+ .map(|p| p.to_path_buf())
371
+ .unwrap_or_else(|| build_dir.join("target"));
372
+ // Default to target-cpu=native so the emitted binary uses every SIMD / ISA
373
+ // 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
+ };
383
+ // Nested `cargo build` (e.g. `tish build --native-backend rust`) inherits the parent
384
+ // environment. CI often sets `RUSTC_WRAPPER=sccache`; wrapping this inner compile too can
385
+ // cause flaky or failed builds (LTO / temp-crate paths). Use plain rustc here; the main
386
+ // workspace build still benefits from the wrapper.
387
+ let mut cmd = Command::new("cargo");
388
+ cmd.args(["build", "--release", "--target-dir"])
291
389
  .arg(&target_dir)
292
390
  .current_dir(build_dir)
293
391
  .env_remove("CARGO_TARGET_DIR")
392
+ .env_remove("RUSTC_WRAPPER")
393
+ .env_remove("RUSTC_WORKSPACE_WRAPPER")
394
+ .env_remove("CARGO_BUILD_RUSTC_WRAPPER")
294
395
  .env("CARGO_TERM_PROGRESS", "always")
396
+ .env("RUSTFLAGS", &merged_rustflags);
397
+ if let Some(protoc) = protoc_for_nested_cargo() {
398
+ cmd.env("PROTOC", protoc);
399
+ }
400
+ let output = cmd
295
401
  .output()
296
402
  .map_err(|e| format!("Failed to run cargo: {}", e))?;
297
403
 
298
404
  if !output.status.success() {
299
405
  let stderr = String::from_utf8_lossy(&output.stderr);
300
406
  let stdout = String::from_utf8_lossy(&output.stdout);
301
- return Err(format!("Compilation failed.\nstdout:\n{}\nstderr:\n{}", stdout, stderr));
407
+ return Err(format!(
408
+ "Compilation failed.\nstdout:\n{}\nstderr:\n{}",
409
+ stdout, stderr
410
+ ));
302
411
  }
303
412
  Ok(())
304
413
  }
305
414
 
415
+ #[cfg(test)]
416
+ mod protoc_tests {
417
+ use super::*;
418
+
419
+ #[test]
420
+ fn protoc_for_nested_cargo_without_env_uses_vendored_or_path() {
421
+ let _lock = std::sync::Mutex::new(());
422
+ let _guard = _lock.lock().unwrap();
423
+ std::env::remove_var("PROTOC");
424
+ 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
+ );
430
+ }
431
+ }
432
+
306
433
  /// Find the built binary in target/release.
307
434
  pub fn find_release_binary(binary_dir: &Path, bin_name: &str) -> Result<PathBuf, String> {
308
435
  let binary_no_ext = binary_dir.join(bin_name);
@@ -351,7 +478,8 @@ pub fn copy_binary_to_output(binary: &Path, output_path: &Path) -> Result<(), St
351
478
  if let Some(parent) = output_path.parent() {
352
479
  fs::create_dir_all(parent).map_err(|e| format!("Cannot create output dir: {}", e))?;
353
480
  }
354
- fs::copy(binary, output_path).map_err(|e| format!("Cannot copy to {}: {}", output_path.display(), e))?;
481
+ fs::copy(binary, output_path)
482
+ .map_err(|e| format!("Cannot copy to {}: {}", output_path.display(), e))?;
355
483
  Ok(())
356
484
  }
357
485
 
@@ -8,9 +8,13 @@ description = "Shared builtin implementations for Tish (array, string, object, m
8
8
  license-file = { workspace = true }
9
9
  repository = { workspace = true }
10
10
  [dependencies]
11
- rand = "0.10.0"
11
+ rand = "0.10.1"
12
12
  tishlang_core = { path = "../tish_core", version = ">=0.1" }
13
13
 
14
14
  [features]
15
15
  default = []
16
16
  regex = ["tishlang_core/regex"]
17
+ # Propagate the `send-values` feature from `tishlang_core` so that closures
18
+ # handed to us via `NativeFn` pick up the `Send + Sync` bound automatically
19
+ # when multi-threaded `Value`s are enabled upstream.
20
+ send-values = ["tishlang_core/send-values"]
@@ -1,13 +1,14 @@
1
1
  //! Array builtin methods.
2
2
 
3
+ use crate::helpers::normalize_index;
4
+ use tishlang_core::VmRef;
3
5
  use std::cell::RefCell;
4
6
  use std::rc::Rc;
5
7
  use tishlang_core::Value;
6
- use crate::helpers::normalize_index;
7
8
 
8
9
  /// Create a new array Value from a Vec of Values.
9
10
  pub fn from_vec(v: Vec<Value>) -> Value {
10
- Value::Array(Rc::new(RefCell::new(v)))
11
+ Value::Array(VmRef::new(v))
11
12
  }
12
13
 
13
14
  /// Get the length of an array.
@@ -110,7 +111,7 @@ pub fn join(arr: &Value, sep: &Value) -> Value {
110
111
  pub fn reverse(arr: &Value) -> Value {
111
112
  if let Value::Array(arr) = arr {
112
113
  arr.borrow_mut().reverse();
113
- Value::Array(Rc::clone(arr))
114
+ Value::Array(arr.clone())
114
115
  } else {
115
116
  Value::Null
116
117
  }
@@ -122,7 +123,7 @@ pub fn shuffle(arr: &Value) -> Value {
122
123
  let mut v = arr.borrow().clone();
123
124
  use rand::seq::SliceRandom;
124
125
  v.shuffle(&mut rand::rng());
125
- Value::Array(Rc::new(RefCell::new(v)))
126
+ Value::Array(VmRef::new(v))
126
127
  } else {
127
128
  Value::Null
128
129
  }
@@ -141,7 +142,7 @@ pub fn splice(arr: &Value, start: &Value, delete_count: Option<&Value>, items: &
141
142
  let removed: Vec<Value> = arr_mut
142
143
  .splice(start_idx..start_idx + actual_delete, items.iter().cloned())
143
144
  .collect();
144
- Value::Array(Rc::new(RefCell::new(removed)))
145
+ Value::Array(VmRef::new(removed))
145
146
  } else {
146
147
  Value::Null
147
148
  }
@@ -153,8 +154,12 @@ pub fn slice(arr: &Value, start: &Value, end: &Value) -> Value {
153
154
  let len = arr_borrow.len() as i64;
154
155
  let start_idx = normalize_index(start, len, 0);
155
156
  let end_idx = normalize_index(end, len, len as usize);
156
- let sliced = if start_idx < end_idx { arr_borrow[start_idx..end_idx].to_vec() } else { vec![] };
157
- Value::Array(Rc::new(RefCell::new(sliced)))
157
+ let sliced = if start_idx < end_idx {
158
+ arr_borrow[start_idx..end_idx].to_vec()
159
+ } else {
160
+ vec![]
161
+ };
162
+ Value::Array(VmRef::new(sliced))
158
163
  } else {
159
164
  Value::Null
160
165
  }
@@ -170,7 +175,7 @@ pub fn concat(arr: &Value, args: &[Value]) -> Value {
170
175
  result.push(v.clone());
171
176
  }
172
177
  }
173
- Value::Array(Rc::new(RefCell::new(result)))
178
+ Value::Array(VmRef::new(result))
174
179
  } else {
175
180
  Value::Null
176
181
  }
@@ -188,7 +193,7 @@ pub fn flat(arr: &Value, depth: &Value) -> Value {
188
193
  result.push(v.clone());
189
194
  }
190
195
  }
191
-
196
+
192
197
  if let Value::Array(arr) = arr {
193
198
  let d = match depth {
194
199
  Value::Number(n) => *n as i32,
@@ -196,7 +201,7 @@ pub fn flat(arr: &Value, depth: &Value) -> Value {
196
201
  };
197
202
  let mut result = Vec::new();
198
203
  flatten(&arr.borrow(), d, &mut result);
199
- Value::Array(Rc::new(RefCell::new(result)))
204
+ Value::Array(VmRef::new(result))
200
205
  } else {
201
206
  Value::Null
202
207
  }
@@ -208,10 +213,12 @@ pub fn flat(arr: &Value, depth: &Value) -> Value {
208
213
  pub fn map(arr: &Value, callback: &Value) -> Value {
209
214
  if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
210
215
  let arr_borrow = arr.borrow();
211
- let result: Vec<Value> = arr_borrow.iter().enumerate().map(|(i, v)| {
212
- cb(&[v.clone(), Value::Number(i as f64)])
213
- }).collect();
214
- Value::Array(Rc::new(RefCell::new(result)))
216
+ let result: Vec<Value> = arr_borrow
217
+ .iter()
218
+ .enumerate()
219
+ .map(|(i, v)| cb(&[v.clone(), Value::Number(i as f64)]))
220
+ .collect();
221
+ Value::Array(VmRef::new(result))
215
222
  } else {
216
223
  Value::Null
217
224
  }
@@ -220,11 +227,19 @@ pub fn map(arr: &Value, callback: &Value) -> Value {
220
227
  pub fn filter(arr: &Value, callback: &Value) -> Value {
221
228
  if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
222
229
  let arr_borrow = arr.borrow();
223
- let result: Vec<Value> = arr_borrow.iter().enumerate().filter_map(|(i, v)| {
224
- let keep = cb(&[v.clone(), Value::Number(i as f64)]);
225
- if keep.is_truthy() { Some(v.clone()) } else { None }
226
- }).collect();
227
- Value::Array(Rc::new(RefCell::new(result)))
230
+ let result: Vec<Value> = arr_borrow
231
+ .iter()
232
+ .enumerate()
233
+ .filter_map(|(i, v)| {
234
+ let keep = cb(&[v.clone(), Value::Number(i as f64)]);
235
+ if keep.is_truthy() {
236
+ Some(v.clone())
237
+ } else {
238
+ None
239
+ }
240
+ })
241
+ .collect();
242
+ Value::Array(VmRef::new(result))
228
243
  } else {
229
244
  Value::Null
230
245
  }
@@ -234,9 +249,7 @@ pub fn reduce(arr: &Value, callback: &Value, initial: &Value) -> Value {
234
249
  if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
235
250
  let arr_borrow = arr.borrow();
236
251
  let len = arr_borrow.len();
237
- let (start_idx, mut acc) = if matches!(initial, Value::Null)
238
- && !arr_borrow.is_empty()
239
- {
252
+ let (start_idx, mut acc) = if matches!(initial, Value::Null) && !arr_borrow.is_empty() {
240
253
  // No initial value: use first element as acc, start from index 1
241
254
  (1, arr_borrow[0].clone())
242
255
  } else {
@@ -328,24 +341,28 @@ pub fn flat_map(arr: &Value, callback: &Value) -> Value {
328
341
  result.push(mapped);
329
342
  }
330
343
  }
331
- Value::Array(Rc::new(RefCell::new(result)))
344
+ Value::Array(VmRef::new(result))
332
345
  } else {
333
346
  Value::Null
334
347
  }
335
348
  }
336
349
 
337
350
  fn sort_by_impl<F>(arr: &Value, cmp: F) -> Value
338
- where F: FnMut(&Value, &Value) -> std::cmp::Ordering {
351
+ where
352
+ F: FnMut(&Value, &Value) -> std::cmp::Ordering,
353
+ {
339
354
  if let Value::Array(arr) = arr {
340
355
  arr.borrow_mut().sort_by(cmp);
341
- Value::Array(Rc::clone(arr))
356
+ Value::Array(arr.clone())
342
357
  } else {
343
358
  Value::Null
344
359
  }
345
360
  }
346
361
 
347
362
  pub fn sort_default(arr: &Value) -> Value {
348
- sort_by_impl(arr, |a, b| a.to_display_string().cmp(&b.to_display_string()))
363
+ sort_by_impl(arr, |a, b| {
364
+ a.to_display_string().cmp(&b.to_display_string())
365
+ })
349
366
  }
350
367
 
351
368
  pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
@@ -355,7 +372,7 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
355
372
  let mut indices: Vec<usize> = (0..len).collect();
356
373
  let mut elements: Vec<Value> = std::mem::take(&mut *arr_mut);
357
374
  let mut args_buf: [Value; 2] = [Value::Null, Value::Null];
358
-
375
+
359
376
  indices.sort_by(|&a, &b| {
360
377
  args_buf[0] = elements[a].clone();
361
378
  args_buf[1] = elements[b].clone();
@@ -365,10 +382,13 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
365
382
  _ => std::cmp::Ordering::Equal,
366
383
  }
367
384
  });
368
-
369
- *arr_mut = indices.into_iter().map(|i| std::mem::replace(&mut elements[i], Value::Null)).collect();
385
+
386
+ *arr_mut = indices
387
+ .into_iter()
388
+ .map(|i| std::mem::replace(&mut elements[i], Value::Null))
389
+ .collect();
370
390
  drop(arr_mut);
371
- Value::Array(Rc::clone(arr))
391
+ Value::Array(arr.clone())
372
392
  } else {
373
393
  Value::Null
374
394
  }
@@ -380,7 +400,11 @@ fn num_cmp(a: &Value, b: &Value, asc: bool) -> std::cmp::Ordering {
380
400
  _ => (f64::NAN, f64::NAN),
381
401
  };
382
402
  let cmp = na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
383
- if asc { cmp } else { cmp.reverse() }
403
+ if asc {
404
+ cmp
405
+ } else {
406
+ cmp.reverse()
407
+ }
384
408
  }
385
409
 
386
410
  pub fn sort_numeric_asc(arr: &Value) -> Value {
@@ -398,13 +422,21 @@ pub fn sort_by_property_numeric(arr: &Value, prop: &str, asc: bool) -> Value {
398
422
  let na = get_prop_number(a, &prop_arc);
399
423
  let nb = get_prop_number(b, &prop_arc);
400
424
  let cmp = na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
401
- if asc { cmp } else { cmp.reverse() }
425
+ if asc {
426
+ cmp
427
+ } else {
428
+ cmp.reverse()
429
+ }
402
430
  })
403
431
  }
404
432
 
405
433
  fn get_prop_number(v: &Value, prop: &std::sync::Arc<str>) -> f64 {
406
434
  match v {
407
- Value::Object(o) => o.borrow().get(prop.as_ref()).map(|v| v.as_number().unwrap_or(f64::NAN)).unwrap_or(f64::NAN),
435
+ Value::Object(o) => o
436
+ .borrow()
437
+ .get(prop.as_ref())
438
+ .map(|v| v.as_number().unwrap_or(f64::NAN))
439
+ .unwrap_or(f64::NAN),
408
440
  _ => f64::NAN,
409
441
  }
410
442
  }