@tishlang/tish-format 1.0.12 → 1.0.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 (164) hide show
  1. package/Cargo.toml +49 -0
  2. package/LICENSE +13 -0
  3. package/README.md +138 -0
  4. package/bin/tish-format +0 -0
  5. package/crates/js_to_tish/Cargo.toml +11 -0
  6. package/crates/js_to_tish/README.md +18 -0
  7. package/crates/js_to_tish/src/error.rs +55 -0
  8. package/crates/js_to_tish/src/lib.rs +11 -0
  9. package/crates/js_to_tish/src/span_util.rs +35 -0
  10. package/crates/js_to_tish/src/transform/expr.rs +610 -0
  11. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  12. package/crates/js_to_tish/src/transform.rs +60 -0
  13. package/crates/tish/Cargo.toml +54 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +565 -0
  16. package/crates/tish/src/main.rs +781 -0
  17. package/crates/tish/src/repl_completion.rs +200 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  24. package/crates/tish/tests/integration_test.rs +1095 -0
  25. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  26. package/crates/tish/tests/shortcircuit.rs +65 -0
  27. package/crates/tish_ast/Cargo.toml +9 -0
  28. package/crates/tish_ast/src/ast.rs +620 -0
  29. package/crates/tish_ast/src/lib.rs +5 -0
  30. package/crates/tish_build_utils/Cargo.toml +11 -0
  31. package/crates/tish_build_utils/src/lib.rs +577 -0
  32. package/crates/tish_builtins/Cargo.toml +20 -0
  33. package/crates/tish_builtins/src/array.rs +441 -0
  34. package/crates/tish_builtins/src/construct.rs +159 -0
  35. package/crates/tish_builtins/src/globals.rs +213 -0
  36. package/crates/tish_builtins/src/helpers.rs +35 -0
  37. package/crates/tish_builtins/src/lib.rs +16 -0
  38. package/crates/tish_builtins/src/math.rs +89 -0
  39. package/crates/tish_builtins/src/object.rs +36 -0
  40. package/crates/tish_builtins/src/string.rs +647 -0
  41. package/crates/tish_builtins/src/symbol.rs +83 -0
  42. package/crates/tish_bytecode/Cargo.toml +17 -0
  43. package/crates/tish_bytecode/src/chunk.rs +96 -0
  44. package/crates/tish_bytecode/src/compiler.rs +1760 -0
  45. package/crates/tish_bytecode/src/encoding.rs +100 -0
  46. package/crates/tish_bytecode/src/lib.rs +19 -0
  47. package/crates/tish_bytecode/src/opcode.rs +142 -0
  48. package/crates/tish_bytecode/src/peephole.rs +189 -0
  49. package/crates/tish_bytecode/src/serialize.rs +163 -0
  50. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  51. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  52. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  53. package/crates/tish_compile/Cargo.toml +26 -0
  54. package/crates/tish_compile/src/codegen.rs +5332 -0
  55. package/crates/tish_compile/src/infer.rs +292 -0
  56. package/crates/tish_compile/src/lib.rs +164 -0
  57. package/crates/tish_compile/src/resolve.rs +1388 -0
  58. package/crates/tish_compile/src/types.rs +501 -0
  59. package/crates/tish_compile_js/Cargo.toml +18 -0
  60. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  61. package/crates/tish_compile_js/src/codegen.rs +871 -0
  62. package/crates/tish_compile_js/src/error.rs +20 -0
  63. package/crates/tish_compile_js/src/lib.rs +26 -0
  64. package/crates/tish_compile_js/src/tests_jsx.rs +350 -0
  65. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  66. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  67. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  68. package/crates/tish_core/Cargo.toml +26 -0
  69. package/crates/tish_core/src/console_style.rs +160 -0
  70. package/crates/tish_core/src/json.rs +387 -0
  71. package/crates/tish_core/src/lib.rs +17 -0
  72. package/crates/tish_core/src/macros.rs +36 -0
  73. package/crates/tish_core/src/uri.rs +118 -0
  74. package/crates/tish_core/src/value.rs +696 -0
  75. package/crates/tish_core/src/vmref.rs +178 -0
  76. package/crates/tish_cranelift/Cargo.toml +19 -0
  77. package/crates/tish_cranelift/src/lib.rs +43 -0
  78. package/crates/tish_cranelift/src/link.rs +117 -0
  79. package/crates/tish_cranelift/src/lower.rs +85 -0
  80. package/crates/tish_cranelift_runtime/Cargo.toml +25 -0
  81. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  82. package/crates/tish_eval/Cargo.toml +45 -0
  83. package/crates/tish_eval/src/eval.rs +3717 -0
  84. package/crates/tish_eval/src/http.rs +188 -0
  85. package/crates/tish_eval/src/lib.rs +99 -0
  86. package/crates/tish_eval/src/natives.rs +399 -0
  87. package/crates/tish_eval/src/promise.rs +179 -0
  88. package/crates/tish_eval/src/regex.rs +299 -0
  89. package/crates/tish_eval/src/timers.rs +120 -0
  90. package/crates/tish_eval/src/value.rs +318 -0
  91. package/crates/tish_eval/src/value_convert.rs +111 -0
  92. package/crates/tish_fmt/Cargo.toml +16 -0
  93. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  94. package/crates/tish_fmt/src/lib.rs +2101 -0
  95. package/crates/tish_jsx_web/Cargo.toml +9 -0
  96. package/crates/tish_jsx_web/README.md +5 -0
  97. package/crates/tish_jsx_web/src/lib.rs +2 -0
  98. package/crates/tish_lexer/Cargo.toml +9 -0
  99. package/crates/tish_lexer/src/lib.rs +716 -0
  100. package/crates/tish_lexer/src/token.rs +163 -0
  101. package/crates/tish_lint/Cargo.toml +18 -0
  102. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  103. package/crates/tish_lint/src/lib.rs +289 -0
  104. package/crates/tish_llvm/Cargo.toml +13 -0
  105. package/crates/tish_llvm/src/lib.rs +115 -0
  106. package/crates/tish_lsp/Cargo.toml +25 -0
  107. package/crates/tish_lsp/README.md +26 -0
  108. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  109. package/crates/tish_lsp/src/import_goto.rs +562 -0
  110. package/crates/tish_lsp/src/main.rs +1046 -0
  111. package/crates/tish_native/Cargo.toml +16 -0
  112. package/crates/tish_native/src/build.rs +427 -0
  113. package/crates/tish_native/src/config.rs +48 -0
  114. package/crates/tish_native/src/lib.rs +416 -0
  115. package/crates/tish_opt/Cargo.toml +13 -0
  116. package/crates/tish_opt/src/lib.rs +943 -0
  117. package/crates/tish_parser/Cargo.toml +11 -0
  118. package/crates/tish_parser/src/lib.rs +332 -0
  119. package/crates/tish_parser/src/parser.rs +2304 -0
  120. package/crates/tish_pg/Cargo.toml +34 -0
  121. package/crates/tish_pg/README.md +38 -0
  122. package/crates/tish_pg/src/error.rs +52 -0
  123. package/crates/tish_pg/src/lib.rs +955 -0
  124. package/crates/tish_resolve/Cargo.toml +13 -0
  125. package/crates/tish_resolve/src/lib.rs +3561 -0
  126. package/crates/tish_resolve/src/pos.rs +141 -0
  127. package/crates/tish_runtime/Cargo.toml +96 -0
  128. package/crates/tish_runtime/src/http.rs +1298 -0
  129. package/crates/tish_runtime/src/http_fetch.rs +471 -0
  130. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  131. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  132. package/crates/tish_runtime/src/lib.rs +1192 -0
  133. package/crates/tish_runtime/src/native_promise.rs +15 -0
  134. package/crates/tish_runtime/src/promise.rs +248 -0
  135. package/crates/tish_runtime/src/promise_io.rs +38 -0
  136. package/crates/tish_runtime/src/timers.rs +166 -0
  137. package/crates/tish_runtime/src/ws.rs +761 -0
  138. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  139. package/crates/tish_ui/Cargo.toml +17 -0
  140. package/crates/tish_ui/src/jsx.rs +682 -0
  141. package/crates/tish_ui/src/lib.rs +20 -0
  142. package/crates/tish_ui/src/runtime/hooks.rs +569 -0
  143. package/crates/tish_ui/src/runtime/mod.rs +180 -0
  144. package/crates/tish_vm/Cargo.toml +47 -0
  145. package/crates/tish_vm/src/lib.rs +39 -0
  146. package/crates/tish_vm/src/vm.rs +2192 -0
  147. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  148. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  149. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  150. package/crates/tish_wasm/Cargo.toml +15 -0
  151. package/crates/tish_wasm/src/lib.rs +424 -0
  152. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  153. package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
  154. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  155. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  156. package/crates/tishlang_cargo_bindgen/src/classify.rs +263 -0
  157. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  158. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  159. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  160. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  161. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  162. package/justfile +268 -0
  163. package/package.json +1 -1
  164. package/platform/darwin-arm64/tish-fmt +0 -0
@@ -0,0 +1,292 @@
1
+ //! Type inference pass: annotates `VarDecl` nodes with inferred `TypeAnnotation`s
2
+ //! where the user hasn't provided them, enabling codegen to emit native Rust types.
3
+ //!
4
+ //! Rules (conservative — only infer when unambiguous):
5
+ //! - Number literal init → `number`
6
+ //! - String literal init → `string`
7
+ //! - Bool literal init → `boolean`
8
+ //! - Arithmetic of two `number` expressions → `number`
9
+ //! - Comparison of two `number` expressions → `boolean`
10
+ //! - Already-annotated vars are left unchanged.
11
+
12
+ use std::collections::HashMap;
13
+ use tishlang_ast::{
14
+ ArrowBody, BinOp, CallArg, Expr, FunParam, Literal, Program, Statement, TypeAnnotation,
15
+ };
16
+
17
+ /// Scoped type environment used during inference.
18
+ #[derive(Default)]
19
+ pub struct InferCtx {
20
+ scopes: Vec<HashMap<String, TypeAnnotation>>,
21
+ }
22
+
23
+ impl InferCtx {
24
+ pub fn new() -> Self {
25
+ Self {
26
+ scopes: vec![HashMap::new()],
27
+ }
28
+ }
29
+
30
+ fn push_scope(&mut self) {
31
+ self.scopes.push(HashMap::new());
32
+ }
33
+
34
+ fn pop_scope(&mut self) {
35
+ self.scopes.pop();
36
+ }
37
+
38
+ fn define(&mut self, name: &str, ty: TypeAnnotation) {
39
+ if let Some(s) = self.scopes.last_mut() {
40
+ s.insert(name.to_string(), ty);
41
+ }
42
+ }
43
+
44
+ pub fn lookup(&self, name: &str) -> Option<&TypeAnnotation> {
45
+ for s in self.scopes.iter().rev() {
46
+ if let Some(t) = s.get(name) {
47
+ return Some(t);
48
+ }
49
+ }
50
+ None
51
+ }
52
+ }
53
+
54
+ fn is_number(ann: &TypeAnnotation) -> bool {
55
+ matches!(ann, TypeAnnotation::Simple(s) if s.as_ref() == "number")
56
+ }
57
+
58
+ fn number_ann() -> TypeAnnotation {
59
+ TypeAnnotation::Simple("number".into())
60
+ }
61
+
62
+ fn string_ann() -> TypeAnnotation {
63
+ TypeAnnotation::Simple("string".into())
64
+ }
65
+
66
+ fn bool_ann() -> TypeAnnotation {
67
+ TypeAnnotation::Simple("boolean".into())
68
+ }
69
+
70
+ /// Infer the `TypeAnnotation` for an expression, if unambiguous.
71
+ pub fn infer_expr_type(expr: &Expr, ctx: &InferCtx) -> Option<TypeAnnotation> {
72
+ match expr {
73
+ Expr::Literal { value, .. } => match value {
74
+ Literal::Number(_) => Some(number_ann()),
75
+ Literal::String(_) => Some(string_ann()),
76
+ Literal::Bool(_) => Some(bool_ann()),
77
+ Literal::Null => None,
78
+ },
79
+ Expr::Ident { name, .. } => ctx.lookup(name.as_ref()).cloned(),
80
+ Expr::Binary {
81
+ left, op, right, ..
82
+ } => {
83
+ let lt = infer_expr_type(left, ctx)?;
84
+ let rt = infer_expr_type(right, ctx)?;
85
+ if is_number(&lt) && is_number(&rt) {
86
+ match op {
87
+ BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod | BinOp::Pow => {
88
+ Some(number_ann())
89
+ }
90
+ BinOp::Lt
91
+ | BinOp::Le
92
+ | BinOp::Gt
93
+ | BinOp::Ge
94
+ | BinOp::StrictEq
95
+ | BinOp::StrictNe => Some(bool_ann()),
96
+ _ => None,
97
+ }
98
+ } else {
99
+ None
100
+ }
101
+ }
102
+ Expr::Unary { op, operand, .. } => {
103
+ use tishlang_ast::UnaryOp;
104
+ match op {
105
+ UnaryOp::Neg | UnaryOp::Pos => {
106
+ let t = infer_expr_type(operand, ctx)?;
107
+ if is_number(&t) {
108
+ Some(number_ann())
109
+ } else {
110
+ None
111
+ }
112
+ }
113
+ UnaryOp::Not => Some(bool_ann()),
114
+ _ => None,
115
+ }
116
+ }
117
+ _ => None,
118
+ }
119
+ }
120
+
121
+ /// Run inference over a program, returning a modified Program with additional
122
+ /// type annotations filled in on `VarDecl` nodes.
123
+ pub fn infer_program(program: &Program) -> Program {
124
+ let mut ctx = InferCtx::new();
125
+ Program {
126
+ statements: infer_statements(&program.statements, &mut ctx),
127
+ }
128
+ }
129
+
130
+ fn infer_statements(stmts: &[Statement], ctx: &mut InferCtx) -> Vec<Statement> {
131
+ stmts.iter().map(|s| infer_statement(s, ctx)).collect()
132
+ }
133
+
134
+ fn infer_statement(stmt: &Statement, ctx: &mut InferCtx) -> Statement {
135
+ match stmt {
136
+ Statement::VarDecl {
137
+ name,
138
+ name_span,
139
+ mutable,
140
+ type_ann,
141
+ init,
142
+ span,
143
+ } => {
144
+ // Already annotated — propagate into ctx but don't change the node.
145
+ if let Some(ann) = type_ann {
146
+ ctx.define(name.as_ref(), ann.clone());
147
+ return stmt.clone();
148
+ }
149
+ // Try to infer from init expression.
150
+ let inferred = init.as_ref().and_then(|e| infer_expr_type(e, ctx));
151
+ if let Some(ref ann) = inferred {
152
+ ctx.define(name.as_ref(), ann.clone());
153
+ }
154
+ Statement::VarDecl {
155
+ name: name.clone(),
156
+ name_span: *name_span,
157
+ mutable: *mutable,
158
+ type_ann: inferred,
159
+ init: init.clone(),
160
+ span: *span,
161
+ }
162
+ }
163
+ Statement::Block { statements, span } => {
164
+ ctx.push_scope();
165
+ let stmts = infer_statements(statements, ctx);
166
+ ctx.pop_scope();
167
+ Statement::Block {
168
+ statements: stmts,
169
+ span: *span,
170
+ }
171
+ }
172
+ Statement::For {
173
+ init,
174
+ cond,
175
+ update,
176
+ body,
177
+ span,
178
+ } => {
179
+ // Scope for loop variable
180
+ ctx.push_scope();
181
+ let new_init = init.as_ref().map(|i| Box::new(infer_statement(i, ctx)));
182
+ let new_body = Box::new(infer_statement(body, ctx));
183
+ ctx.pop_scope();
184
+ Statement::For {
185
+ init: new_init,
186
+ cond: cond.clone(),
187
+ update: update.clone(),
188
+ body: new_body,
189
+ span: *span,
190
+ }
191
+ }
192
+ Statement::ForOf {
193
+ name,
194
+ name_span,
195
+ iterable,
196
+ body,
197
+ span,
198
+ } => {
199
+ ctx.push_scope();
200
+ let new_body = Box::new(infer_statement(body, ctx));
201
+ ctx.pop_scope();
202
+ Statement::ForOf {
203
+ name: name.clone(),
204
+ name_span: *name_span,
205
+ iterable: iterable.clone(),
206
+ body: new_body,
207
+ span: *span,
208
+ }
209
+ }
210
+ Statement::While { cond, body, span } => {
211
+ ctx.push_scope();
212
+ let new_body = Box::new(infer_statement(body, ctx));
213
+ ctx.pop_scope();
214
+ Statement::While {
215
+ cond: cond.clone(),
216
+ body: new_body,
217
+ span: *span,
218
+ }
219
+ }
220
+ Statement::DoWhile { body, cond, span } => {
221
+ ctx.push_scope();
222
+ let new_body = Box::new(infer_statement(body, ctx));
223
+ ctx.pop_scope();
224
+ Statement::DoWhile {
225
+ body: new_body,
226
+ cond: cond.clone(),
227
+ span: *span,
228
+ }
229
+ }
230
+ Statement::If {
231
+ cond,
232
+ then_branch,
233
+ else_branch,
234
+ span,
235
+ } => {
236
+ let new_then = Box::new(infer_statement(then_branch, ctx));
237
+ let new_else = else_branch
238
+ .as_ref()
239
+ .map(|e| Box::new(infer_statement(e, ctx)));
240
+ Statement::If {
241
+ cond: cond.clone(),
242
+ then_branch: new_then,
243
+ else_branch: new_else,
244
+ span: *span,
245
+ }
246
+ }
247
+ Statement::FunDecl {
248
+ async_,
249
+ name,
250
+ name_span,
251
+ params,
252
+ rest_param,
253
+ return_type,
254
+ body,
255
+ span,
256
+ } => {
257
+ ctx.push_scope();
258
+ for p in params {
259
+ if let FunParam::Simple(tp) = p {
260
+ if let Some(ann) = &tp.type_ann {
261
+ ctx.define(tp.name.as_ref(), ann.clone());
262
+ }
263
+ }
264
+ }
265
+ if let Some(rp) = rest_param {
266
+ if let Some(ann) = &rp.type_ann {
267
+ ctx.define(rp.name.as_ref(), ann.clone());
268
+ }
269
+ }
270
+ let new_body = Box::new(infer_statement(body, ctx));
271
+ ctx.pop_scope();
272
+ Statement::FunDecl {
273
+ async_: *async_,
274
+ name: name.clone(),
275
+ name_span: *name_span,
276
+ params: params.clone(),
277
+ rest_param: rest_param.clone(),
278
+ return_type: return_type.clone(),
279
+ body: new_body,
280
+ span: *span,
281
+ }
282
+ }
283
+ // For statements with no interesting sub-structure, clone as-is.
284
+ _ => stmt.clone(),
285
+ }
286
+ }
287
+
288
+ // Suppress unused import warning — CallArg is used indirectly via tishlang_ast.
289
+ #[allow(dead_code)]
290
+ fn _uses_call_arg(_: &CallArg) {}
291
+ #[allow(dead_code)]
292
+ fn _uses_arrow_body(_: &ArrowBody) {}
@@ -0,0 +1,164 @@
1
+ //! Native compiler backend for Tish.
2
+ //!
3
+ //! Emits Rust source that links to tishlang_runtime.
4
+
5
+ mod codegen;
6
+ mod infer;
7
+ mod resolve;
8
+ mod types;
9
+
10
+ /// How generated Rust is linked (desktop binary vs embedded iOS staticlib).
11
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12
+ pub enum NativeEmitMode {
13
+ #[default]
14
+ DesktopBin,
15
+ /// `[lib] crate-type = ["staticlib"]` — no `fn main()`, host calls `tish_ios_launch`.
16
+ EmbeddedLib,
17
+ }
18
+
19
+ pub use codegen::CompileError;
20
+ pub use codegen::{
21
+ compile, compile_project, compile_project_full, compile_project_full_emit,
22
+ compile_with_features, compile_with_native_modules, compile_with_native_modules_emit,
23
+ compile_with_project_root,
24
+ };
25
+ pub use resolve::{
26
+ cargo_export_fn_name, compute_native_build_artifacts, detect_cycles, ensure_tish_canvas_module,
27
+ export_name_to_rust_ident, extract_native_import_features, format_rust_dependencies_toml,
28
+ generate_native_wrapper_rs, has_external_native_imports, has_native_imports,
29
+ infer_native_module_exports, is_builtin_native_spec, is_cargo_native_spec, is_native_import,
30
+ merge_modules, normalize_builtin_spec, program_uses_document, read_project_tish_config,
31
+ resolve_bare_spec, resolve_native_modules, resolve_project, resolve_project_from_stdin,
32
+ MergedProgram, NativeBuildArtifacts, NativeModuleInit, ResolvedNativeModule,
33
+ };
34
+ pub use types::{RustType, TypeContext};
35
+
36
+ #[cfg(test)]
37
+ mod tests {
38
+ use super::*;
39
+ use tishlang_parser::parse;
40
+
41
+ #[test]
42
+ fn typed_assign_conversion() {
43
+ // With the inference pass and native emit, `total: number = 0` becomes f64.
44
+ // Assignment `total = total + n` (where n comes from ForOf over a Value::Array)
45
+ // emits a native f64 assignment that unboxes the Value result via from_value_expr.
46
+ let src = r#"
47
+ fn sum(...args: number[]): number {
48
+ let total: number = 0
49
+ for (let n of args) { total = total + n }
50
+ return total
51
+ }
52
+ "#;
53
+ let program = parse(src).unwrap();
54
+ let rust = compile(&program).unwrap();
55
+ // total should be declared as f64
56
+ assert!(rust.contains("let mut total: f64"), "expected total: f64");
57
+ // The return value of run() should convert total back to Value
58
+ assert!(
59
+ rust.contains("Value::Number(total)"),
60
+ "expected Value::Number(total) wrapping"
61
+ );
62
+ }
63
+
64
+ #[test]
65
+ fn loop_var_decl_clone_outer_var() {
66
+ // With inference, outerVar = 42 gets inferred as f64. f64 is Copy, so no clone is
67
+ // needed — direct assignment is correct. The test verifies compilation succeeds.
68
+ let src = r#"
69
+ let outerVar = 42
70
+ for (let i = 0; i < 5; i = i + 1) {
71
+ let x = outerVar
72
+ }
73
+ "#;
74
+ let program = parse(src).unwrap();
75
+ let rust = compile(&program).unwrap();
76
+ // outerVar and x are f64 (inferred) — Copy assignment, no .clone() needed.
77
+ assert!(
78
+ rust.contains("let mut outerVar: f64"),
79
+ "expected outerVar: f64"
80
+ );
81
+ assert!(rust.contains("let mut x: f64"), "expected x: f64");
82
+ }
83
+
84
+ #[test]
85
+ fn new_expression_lowers_to_construct_on_native() {
86
+ let src = "fn f() { return new Uint8Array(4) }";
87
+ let program = parse(src).unwrap();
88
+ let rust = compile(&program).unwrap();
89
+ assert!(
90
+ rust.contains("tish_construct"),
91
+ "expected new to lower to tish_construct, got snippet missing it"
92
+ );
93
+ }
94
+
95
+ /// User-defined constructor name: `new ClassName(...)` must compile natively (host `construct`)
96
+ /// and is the same surface syntax as the JS target (`new` in emitted JavaScript).
97
+ #[test]
98
+ fn new_class_name_compiles_native_via_tish_construct() {
99
+ let src = r#"
100
+ fn ClassName(x) {
101
+ return x
102
+ }
103
+ fn factory() {
104
+ return new ClassName(42)
105
+ }
106
+ "#;
107
+ let program = parse(src).unwrap();
108
+ let rust = compile(&program).unwrap();
109
+ assert!(
110
+ rust.contains("tish_construct"),
111
+ "expected new ClassName to lower to tish_construct"
112
+ );
113
+ assert!(
114
+ rust.contains("ClassName"),
115
+ "expected emitted Rust to reference ClassName callable"
116
+ );
117
+ }
118
+
119
+ /// `value_call` must take `&Value` to a **local** (`let _callee = (<expr>).clone(); … &_callee`):
120
+ /// `&<temporary>` can dangle in release, and `let _callee = <ident>` would move globals like `Symbol`.
121
+ #[test]
122
+ fn native_emit_value_call_materializes_callee() {
123
+ use std::path::PathBuf;
124
+ let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
125
+ let path = manifest.join("../../tests/core/symbol.tish").canonicalize().unwrap();
126
+ let (rust, _, _, _) = compile_project_full(&path, path.parent(), &[], true).unwrap();
127
+ assert!(
128
+ rust.contains("let _callee = (tishlang_runtime::get_index"),
129
+ "fixture should bracket-call via get_index with callee stored in a local"
130
+ );
131
+ assert!(
132
+ !rust.contains("let _callee = &tishlang_runtime::get_index"),
133
+ "expected callee materialization, found reference-to-temporary pattern"
134
+ );
135
+ assert!(
136
+ rust.contains("tishlang_runtime::value_call"),
137
+ "expected value_call via runtime re-export for nested Cargo builds"
138
+ );
139
+ }
140
+
141
+ #[test]
142
+ fn loop_var_decl_clone_via_project_full() {
143
+ // With the inference pass, `let outerVar = 42` is inferred as f64 (Copy) — no clone needed.
144
+ // This test verifies the full benchmark_granular project compiles and that outerVar
145
+ // is emitted as the inferred f64 type rather than requiring a Value clone.
146
+ let manifest = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
147
+ let bench = manifest
148
+ .join("../../tests/core/benchmark_granular.tish")
149
+ .canonicalize()
150
+ .unwrap();
151
+ // Use same default features as tish CLI (http, fs, process, regex)
152
+ let features = ["http", "fs", "process", "regex"]
153
+ .into_iter()
154
+ .map(String::from)
155
+ .collect::<Vec<_>>();
156
+ let (rust, _, _, _) =
157
+ compile_project_full(&bench, bench.parent(), &features, true).unwrap();
158
+ // outerVar = 42 is inferred as f64; f64 is Copy so no .clone() is emitted.
159
+ assert!(
160
+ rust.contains("let mut outerVar: f64"),
161
+ "expected outerVar to be inferred as f64 (Copy, no clone needed)"
162
+ );
163
+ }
164
+ }