@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,501 @@
1
+ //! Type system for Tish static typing.
2
+ //!
3
+ //! Maps TypeAnnotation from the AST to concrete Rust types for code generation.
4
+
5
+ use std::collections::HashMap;
6
+ use std::sync::Arc;
7
+ use tishlang_ast::{BinOp, FunParam, TypeAnnotation, TypedParam};
8
+
9
+ /// Concrete Rust type representation for code generation.
10
+ #[derive(Debug, Clone, PartialEq)]
11
+ pub enum RustType {
12
+ /// Dynamic Value type (untyped or complex types)
13
+ Value,
14
+ /// f64 (for number)
15
+ F64,
16
+ /// String (for string)
17
+ String,
18
+ /// bool (for boolean)
19
+ Bool,
20
+ /// () for void/null
21
+ Unit,
22
+ /// Vec<T> for arrays
23
+ Vec(Box<RustType>),
24
+ /// Option<T> for nullable types (T | null)
25
+ Option(Box<RustType>),
26
+ /// Inline object shape — used during inference / annotation lowering
27
+ /// before a `Named` alias has been registered. Once a corresponding
28
+ /// `type Foo = { ... }` declaration is found in the program, occurrences
29
+ /// of this shape can be canonicalised into `RustType::Named("Foo")`.
30
+ Object(Vec<(Arc<str>, RustType)>),
31
+ /// User-defined named type (a struct emitted by the compiler).
32
+ /// The field list is duplicated here so the codegen can emit struct
33
+ /// literals, member access, and Value-conversion glue without going
34
+ /// back to a global registry on every call site.
35
+ Named {
36
+ name: Arc<str>,
37
+ fields: Vec<(Arc<str>, RustType)>,
38
+ },
39
+ /// Fn trait for typed functions
40
+ Function {
41
+ params: Vec<RustType>,
42
+ returns: Box<RustType>,
43
+ },
44
+ }
45
+
46
+ impl RustType {
47
+ /// Convert a TypeAnnotation to a RustType (no alias resolution).
48
+ /// Use [`Self::from_annotation_with_aliases`] when a registry is
49
+ /// available so user-defined `type X = { ... }` aliases land as
50
+ /// `RustType::Named` and can drive struct emission.
51
+ pub fn from_annotation(ann: &TypeAnnotation) -> Self {
52
+ Self::from_annotation_with_aliases(ann, &HashMap::new())
53
+ }
54
+
55
+ /// Like [`from_annotation`], but consults `aliases` so a `Simple(name)`
56
+ /// reference to a user-declared `type X = { ... }` resolves to a
57
+ /// `RustType::Named { name, fields }` carrying the struct shape.
58
+ pub fn from_annotation_with_aliases(
59
+ ann: &TypeAnnotation,
60
+ aliases: &HashMap<String, RustType>,
61
+ ) -> Self {
62
+ match ann {
63
+ TypeAnnotation::Simple(name) => match name.as_ref() {
64
+ "number" => RustType::F64,
65
+ "string" => RustType::String,
66
+ "boolean" | "bool" => RustType::Bool,
67
+ "void" | "undefined" => RustType::Unit,
68
+ "null" => RustType::Unit,
69
+ "any" => RustType::Value,
70
+ other => {
71
+ // User-declared `type X = { ... }`: lift the inline
72
+ // object shape into a `Named` so the codegen can emit
73
+ // a Rust struct and direct field access for it.
74
+ if let Some(t) = aliases.get(other) {
75
+ if let RustType::Object(fields) = t {
76
+ return RustType::Named {
77
+ name: Arc::from(other),
78
+ fields: fields.clone(),
79
+ };
80
+ }
81
+ return t.clone();
82
+ }
83
+ RustType::Value
84
+ }
85
+ },
86
+ TypeAnnotation::Array(elem) => {
87
+ RustType::Vec(Box::new(Self::from_annotation_with_aliases(elem, aliases)))
88
+ }
89
+ TypeAnnotation::Object(fields) => {
90
+ let typed_fields: Vec<_> = fields
91
+ .iter()
92
+ .map(|(k, v)| (k.clone(), Self::from_annotation_with_aliases(v, aliases)))
93
+ .collect();
94
+ RustType::Object(typed_fields)
95
+ }
96
+ TypeAnnotation::Function { params, returns } => {
97
+ let typed_params: Vec<_> = params
98
+ .iter()
99
+ .map(|p| Self::from_annotation_with_aliases(p, aliases))
100
+ .collect();
101
+ let typed_returns = Box::new(Self::from_annotation_with_aliases(returns, aliases));
102
+ RustType::Function {
103
+ params: typed_params,
104
+ returns: typed_returns,
105
+ }
106
+ }
107
+ TypeAnnotation::Union(types) => {
108
+ // Check for T | null pattern -> Option<T>
109
+ if types.len() == 2 {
110
+ let has_null = types
111
+ .iter()
112
+ .any(|t| matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null"));
113
+ if has_null {
114
+ let non_null = types.iter().find(
115
+ |t| !matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null"),
116
+ );
117
+ if let Some(inner) = non_null {
118
+ return RustType::Option(Box::new(Self::from_annotation_with_aliases(
119
+ inner, aliases,
120
+ )));
121
+ }
122
+ }
123
+ }
124
+ // Other unions fall back to Value
125
+ RustType::Value
126
+ }
127
+ }
128
+ }
129
+
130
+ /// Check if this type is a native Rust type (not Value).
131
+ pub fn is_native(&self) -> bool {
132
+ !matches!(self, RustType::Value)
133
+ }
134
+
135
+ /// Check if this type is numeric (f64).
136
+ pub fn is_numeric(&self) -> bool {
137
+ matches!(self, RustType::F64)
138
+ }
139
+
140
+ /// Infer the result type of a binary operation given the operand types.
141
+ /// Returns `None` if native code cannot be emitted (fall back to Value path).
142
+ pub fn result_type_of_binop(op: BinOp, lhs: &RustType, rhs: &RustType) -> Option<RustType> {
143
+ if lhs == &RustType::F64 && rhs == &RustType::F64 {
144
+ match op {
145
+ BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod | BinOp::Pow => {
146
+ Some(RustType::F64)
147
+ }
148
+ BinOp::Lt
149
+ | BinOp::Le
150
+ | BinOp::Gt
151
+ | BinOp::Ge
152
+ | BinOp::StrictEq
153
+ | BinOp::StrictNe => Some(RustType::Bool),
154
+ _ => None,
155
+ }
156
+ } else if lhs == &RustType::Bool && rhs == &RustType::Bool {
157
+ match op {
158
+ BinOp::And | BinOp::Or => Some(RustType::Bool),
159
+ BinOp::StrictEq | BinOp::StrictNe => Some(RustType::Bool),
160
+ _ => None,
161
+ }
162
+ } else {
163
+ None
164
+ }
165
+ }
166
+
167
+ /// Get the Rust type string for code generation.
168
+ pub fn to_rust_type_str(&self) -> String {
169
+ match self {
170
+ RustType::Value => "Value".to_string(),
171
+ RustType::F64 => "f64".to_string(),
172
+ RustType::String => "String".to_string(),
173
+ RustType::Bool => "bool".to_string(),
174
+ RustType::Unit => "()".to_string(),
175
+ RustType::Vec(inner) => format!("Vec<{}>", inner.to_rust_type_str()),
176
+ RustType::Option(inner) => format!("Option<{}>", inner.to_rust_type_str()),
177
+ RustType::Object(_) => {
178
+ // Anonymous inline shapes don't have a Rust struct; fall
179
+ // back to the dynamic Value path.
180
+ "Value".to_string()
181
+ }
182
+ RustType::Named { name, .. } => named_struct_ident(name),
183
+ RustType::Function { params, returns } => {
184
+ let params_str: Vec<_> = params.iter().map(|p| p.to_rust_type_str()).collect();
185
+ format!(
186
+ "Rc<dyn Fn({}) -> {}>",
187
+ params_str.join(", "),
188
+ returns.to_rust_type_str()
189
+ )
190
+ }
191
+ }
192
+ }
193
+
194
+ /// Get the default value for this type.
195
+ pub fn default_value(&self) -> String {
196
+ match self {
197
+ RustType::Value => "Value::Null".to_string(),
198
+ RustType::F64 => "0.0".to_string(),
199
+ RustType::String => "String::new()".to_string(),
200
+ RustType::Bool => "false".to_string(),
201
+ RustType::Unit => "()".to_string(),
202
+ RustType::Vec(_) => "Vec::new()".to_string(),
203
+ RustType::Option(_) => "None".to_string(),
204
+ RustType::Object(_) => "Value::Null".to_string(),
205
+ RustType::Named { fields, .. } => {
206
+ // Build a literal struct with each field at its own default,
207
+ // so unannotated decls of a typed struct still compile.
208
+ let init = fields
209
+ .iter()
210
+ .map(|(k, t)| format!("{}: {}", field_ident(k), t.default_value()))
211
+ .collect::<Vec<_>>()
212
+ .join(", ");
213
+ format!(
214
+ "{} {{ {} }}",
215
+ named_struct_ident(match self {
216
+ RustType::Named { name, .. } => name,
217
+ _ => unreachable!(),
218
+ }),
219
+ init
220
+ )
221
+ }
222
+ RustType::Function { .. } => "Value::Null".to_string(),
223
+ }
224
+ }
225
+
226
+ /// Generate code to convert from Value to this native type.
227
+ pub fn from_value_expr(&self, value_expr: &str) -> String {
228
+ match self {
229
+ RustType::Value => value_expr.to_string(),
230
+ RustType::F64 => format!(
231
+ "match &{} {{ Value::Number(n) => *n, _ => panic!(\"expected number\") }}",
232
+ value_expr
233
+ ),
234
+ RustType::String => format!(
235
+ "match &{} {{ Value::String(s) => s.to_string(), _ => panic!(\"expected string\") }}",
236
+ value_expr
237
+ ),
238
+ RustType::Bool => format!(
239
+ "match &{} {{ Value::Bool(b) => *b, _ => panic!(\"expected boolean\") }}",
240
+ value_expr
241
+ ),
242
+ RustType::Unit => "()".to_string(),
243
+ RustType::Vec(inner) => {
244
+ let inner_conversion = inner.from_value_expr("v");
245
+ format!(
246
+ "match &{} {{ Value::Array(arr) => arr.borrow().iter().map(|v| {}).collect(), _ => panic!(\"expected array\") }}",
247
+ value_expr, inner_conversion
248
+ )
249
+ }
250
+ RustType::Option(inner) => {
251
+ let inner_conversion = inner.from_value_expr(value_expr);
252
+ format!(
253
+ "match &{} {{ Value::Null => None, _ => Some({}) }}",
254
+ value_expr, inner_conversion
255
+ )
256
+ }
257
+ RustType::Named { name, fields } => {
258
+ // Each field is fetched out of the Value::Object via
259
+ // `get_prop` and converted to its native type. Falls back
260
+ // to the field's `default_value()` if the field is absent
261
+ // (rare — usually these come from JSON or PG).
262
+ let field_assigns = fields
263
+ .iter()
264
+ .map(|(k, ty)| {
265
+ let fetch =
266
+ format!("tishlang_runtime::get_prop(&{}, {:?})", value_expr, k.as_ref());
267
+ format!("{}: {}", field_ident(k), ty.from_value_expr(&fetch))
268
+ })
269
+ .collect::<Vec<_>>()
270
+ .join(", ");
271
+ format!("{} {{ {} }}", named_struct_ident(name), field_assigns)
272
+ }
273
+ _ => value_expr.to_string(), // Fallback
274
+ }
275
+ }
276
+
277
+ /// Generate code to convert from this native type to Value.
278
+ pub fn to_value_expr(&self, native_expr: &str) -> String {
279
+ match self {
280
+ RustType::Value => native_expr.to_string(),
281
+ RustType::F64 => format!("Value::Number({})", native_expr),
282
+ RustType::String => format!("Value::String({}.clone().into())", native_expr),
283
+ RustType::Bool => format!("Value::Bool({})", native_expr),
284
+ RustType::Unit => "Value::Null".to_string(),
285
+ RustType::Vec(inner) => {
286
+ // Use iter()/copied()/cloned() to avoid moving the vector.
287
+ let (iter_suffix, val_expr) = match inner.as_ref() {
288
+ RustType::F64 => (".iter().copied()", "Value::Number(v)".to_string()),
289
+ RustType::Bool => (".iter().copied()", "Value::Bool(v)".to_string()),
290
+ _ => (".iter().cloned()", inner.to_value_expr("v")),
291
+ };
292
+ format!(
293
+ "Value::Array(VmRef::new({}{}.map(|v| {}).collect()))",
294
+ native_expr, iter_suffix, val_expr
295
+ )
296
+ }
297
+ RustType::Option(inner) => {
298
+ let inner_to_value = inner.to_value_expr("v");
299
+ format!(
300
+ "match {} {{ Some(v) => {}, None => Value::Null }}",
301
+ native_expr, inner_to_value
302
+ )
303
+ }
304
+ RustType::Named { fields, .. } => {
305
+ // Walk fields, build an ObjectMap, wrap in Value::Object.
306
+ // The boundary is paid only when crossing into untyped
307
+ // Tish (JSON.stringify, calling a Value::Function, etc.);
308
+ // direct Rust-to-Rust paths between two Named values stay
309
+ // as plain struct moves.
310
+ let inserts = fields
311
+ .iter()
312
+ .map(|(k, ty)| {
313
+ let access = format!("{}.{}", native_expr, field_ident(k));
314
+ let v_expr = ty.to_value_expr(&access);
315
+ format!(
316
+ "_om.insert(::std::sync::Arc::from({:?}), {});",
317
+ k.as_ref(),
318
+ v_expr
319
+ )
320
+ })
321
+ .collect::<Vec<_>>()
322
+ .join(" ");
323
+ format!(
324
+ "{{ let mut _om = ObjectMap::default(); {} Value::Object(VmRef::new(_om)) }}",
325
+ inserts
326
+ )
327
+ }
328
+ _ => native_expr.to_string(), // Fallback
329
+ }
330
+ }
331
+ }
332
+
333
+ /// Map a Tish type-alias name to the Rust struct identifier we emit.
334
+ /// Prefixed so user names can never collide with runtime types like `Value`.
335
+ pub fn named_struct_ident(tish_name: &str) -> String {
336
+ format!("TishStruct_{}", tish_name)
337
+ }
338
+
339
+ /// Map a Tish field name (`randomNumber`) to a valid Rust identifier
340
+ /// (kept identical here — non-snake-case is allowed via
341
+ /// `#[allow(non_snake_case)]` on the struct, so JS-style camelCase keys
342
+ /// stay readable in the generated source).
343
+ pub fn field_ident(tish_name: &str) -> String {
344
+ // Reserve Rust keywords that would otherwise conflict.
345
+ match tish_name {
346
+ "type" | "ref" | "fn" | "match" | "move" | "mod" | "self" | "Self" | "super" | "use"
347
+ | "where" | "loop" | "yield" | "async" | "await" | "dyn" | "impl" | "trait" | "in"
348
+ | "as" | "box" | "crate" | "const" | "extern" | "let" | "mut" | "pub" | "static"
349
+ | "unsafe" | "abstract" | "become" | "do" | "final" | "macro" | "override" | "priv"
350
+ | "typeof" | "unsized" | "virtual" => format!("r#{}", tish_name),
351
+ _ => tish_name.to_string(),
352
+ }
353
+ }
354
+
355
+ /// Type context for tracking variable types during code generation.
356
+ #[derive(Debug, Clone, Default)]
357
+ pub struct TypeContext {
358
+ /// Stack of scopes, each mapping variable names to their types
359
+ scopes: Vec<HashMap<String, RustType>>,
360
+ }
361
+
362
+ impl TypeContext {
363
+ pub fn new() -> Self {
364
+ Self {
365
+ scopes: vec![HashMap::new()], // Start with global scope
366
+ }
367
+ }
368
+
369
+ /// Enter a new scope (e.g., function body, block).
370
+ pub fn push_scope(&mut self) {
371
+ self.scopes.push(HashMap::new());
372
+ }
373
+
374
+ /// Exit the current scope.
375
+ pub fn pop_scope(&mut self) {
376
+ self.scopes.pop();
377
+ }
378
+
379
+ /// Push a scope for a function or arrow body and record formals as [`RustType::Value`].
380
+ ///
381
+ /// Native codegen always binds parameters from `args.get(i)` as `Value`; this prevents
382
+ /// outer locals (e.g. a loop counter inferred as [`RustType::F64`]) from shadowing the
383
+ /// wrong type for the same identifier.
384
+ pub fn push_fun_param_scope(&mut self, params: &[FunParam], rest_param: Option<&TypedParam>) {
385
+ self.push_scope();
386
+ for p in params {
387
+ for name in p.bound_names() {
388
+ self.define(name.as_ref(), RustType::Value);
389
+ }
390
+ }
391
+ if let Some(rp) = rest_param {
392
+ self.define(rp.name.as_ref(), RustType::Value);
393
+ }
394
+ }
395
+
396
+ /// Define a variable in the current scope.
397
+ pub fn define(&mut self, name: &str, ty: RustType) {
398
+ if let Some(scope) = self.scopes.last_mut() {
399
+ scope.insert(name.to_string(), ty);
400
+ }
401
+ }
402
+
403
+ /// Look up a variable's type (searches from innermost to outermost scope).
404
+ pub fn lookup(&self, name: &str) -> Option<&RustType> {
405
+ for scope in self.scopes.iter().rev() {
406
+ if let Some(ty) = scope.get(name) {
407
+ return Some(ty);
408
+ }
409
+ }
410
+ None
411
+ }
412
+
413
+ /// Check if a variable is typed (has a non-Value type).
414
+ pub fn is_typed(&self, name: &str) -> bool {
415
+ self.lookup(name).map(|ty| ty.is_native()).unwrap_or(false)
416
+ }
417
+
418
+ /// Get the type of a variable, defaulting to Value if not found.
419
+ pub fn get_type(&self, name: &str) -> RustType {
420
+ self.lookup(name).cloned().unwrap_or(RustType::Value)
421
+ }
422
+ }
423
+
424
+ #[cfg(test)]
425
+ mod tests {
426
+ use super::*;
427
+
428
+ #[test]
429
+ fn test_simple_types() {
430
+ assert_eq!(
431
+ RustType::from_annotation(&TypeAnnotation::Simple("number".into())),
432
+ RustType::F64
433
+ );
434
+ assert_eq!(
435
+ RustType::from_annotation(&TypeAnnotation::Simple("string".into())),
436
+ RustType::String
437
+ );
438
+ assert_eq!(
439
+ RustType::from_annotation(&TypeAnnotation::Simple("boolean".into())),
440
+ RustType::Bool
441
+ );
442
+ }
443
+
444
+ #[test]
445
+ fn test_array_type() {
446
+ let arr_type = TypeAnnotation::Array(Box::new(TypeAnnotation::Simple("number".into())));
447
+ assert_eq!(
448
+ RustType::from_annotation(&arr_type),
449
+ RustType::Vec(Box::new(RustType::F64))
450
+ );
451
+ }
452
+
453
+ #[test]
454
+ fn test_nullable_type() {
455
+ let nullable = TypeAnnotation::Union(vec![
456
+ TypeAnnotation::Simple("string".into()),
457
+ TypeAnnotation::Simple("null".into()),
458
+ ]);
459
+ assert_eq!(
460
+ RustType::from_annotation(&nullable),
461
+ RustType::Option(Box::new(RustType::String))
462
+ );
463
+ }
464
+
465
+ #[test]
466
+ fn test_type_context() {
467
+ let mut ctx = TypeContext::new();
468
+ ctx.define("x", RustType::F64);
469
+ assert_eq!(ctx.get_type("x"), RustType::F64);
470
+ assert!(ctx.is_typed("x"));
471
+
472
+ ctx.push_scope();
473
+ ctx.define("y", RustType::String);
474
+ assert_eq!(ctx.get_type("y"), RustType::String);
475
+ assert_eq!(ctx.get_type("x"), RustType::F64); // Can still see outer scope
476
+
477
+ ctx.pop_scope();
478
+ assert_eq!(ctx.get_type("y"), RustType::Value); // y no longer visible
479
+ }
480
+
481
+ #[test]
482
+ fn push_fun_param_scope_shadows_outer() {
483
+ use tishlang_ast::{FunParam, Span, TypedParam};
484
+
485
+ let mut ctx = TypeContext::new();
486
+ ctx.define("n", RustType::F64);
487
+ let params = vec![FunParam::Simple(TypedParam {
488
+ name: "n".into(),
489
+ name_span: Span {
490
+ start: (0, 0),
491
+ end: (0, 0),
492
+ },
493
+ type_ann: None,
494
+ default: None,
495
+ })];
496
+ ctx.push_fun_param_scope(&params, None);
497
+ assert_eq!(ctx.get_type("n"), RustType::Value);
498
+ ctx.pop_scope();
499
+ assert_eq!(ctx.get_type("n"), RustType::F64);
500
+ }
501
+ }
@@ -0,0 +1,18 @@
1
+ [package]
2
+ name = "tishlang_compile_js"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "Tish to JavaScript transpiler backend"
6
+
7
+ license-file = { workspace = true }
8
+ repository = { workspace = true }
9
+ [features]
10
+ default = []
11
+
12
+ [dependencies]
13
+ sourcemap = "9.3"
14
+ tishlang_ast = { path = "../tish_ast", version = ">=0.1" }
15
+ tishlang_compile = { path = "../tish_compile", version = ">=0.1" }
16
+ tishlang_opt = { path = "../tish_opt", version = ">=0.1" }
17
+ tishlang_parser = { path = "../tish_parser", version = ">=0.1" }
18
+ tishlang_ui = { path = "../tish_ui", version = ">=0.1", default-features = false, features = ["compiler"] }
@@ -0,0 +1,8 @@
1
+ // Smoke: JSX + vdom compile (used by CI / manual verify)
2
+ fn Root() {
3
+ return <p class="smoke">{"ok"}</p>
4
+ }
5
+
6
+ export fn main() {
7
+ return 0
8
+ }