@tishlang/tish 1.6.0 → 1.7.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.
- package/Cargo.toml +1 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/error.rs +2 -8
- package/crates/js_to_tish/src/transform/expr.rs +101 -130
- package/crates/js_to_tish/src/transform/stmt.rs +25 -22
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/cli_help.rs +76 -29
- package/crates/tish/src/main.rs +85 -54
- package/crates/tish/tests/cargo_example_compile.rs +3 -1
- package/crates/tish/tests/integration_test.rs +197 -47
- package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
- package/crates/tish/tests/shortcircuit.rs +19 -4
- package/crates/tish_ast/src/ast.rs +12 -14
- package/crates/tish_build_utils/src/lib.rs +31 -6
- package/crates/tish_builtins/src/array.rs +52 -21
- package/crates/tish_builtins/src/construct.rs +2 -8
- package/crates/tish_builtins/src/globals.rs +30 -15
- package/crates/tish_builtins/src/lib.rs +5 -5
- package/crates/tish_builtins/src/math.rs +5 -3
- package/crates/tish_builtins/src/string.rs +71 -19
- package/crates/tish_bytecode/src/chunk.rs +0 -1
- package/crates/tish_bytecode/src/compiler.rs +164 -60
- package/crates/tish_bytecode/src/opcode.rs +13 -4
- package/crates/tish_bytecode/src/peephole.rs +2 -2
- package/crates/tish_compile/src/codegen.rs +921 -299
- package/crates/tish_compile/src/infer.rs +69 -19
- package/crates/tish_compile/src/lib.rs +15 -5
- package/crates/tish_compile/src/resolve.rs +112 -69
- package/crates/tish_compile/src/types.rs +10 -14
- package/crates/tish_compile_js/src/codegen.rs +34 -13
- package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
- package/crates/tish_compiler_wasm/src/lib.rs +16 -13
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +39 -48
- package/crates/tish_core/src/json.rs +5 -3
- package/crates/tish_core/src/lib.rs +1 -1
- package/crates/tish_core/src/uri.rs +9 -6
- package/crates/tish_core/src/value.rs +92 -28
- package/crates/tish_cranelift/src/link.rs +6 -9
- package/crates/tish_cranelift/src/lower.rs +14 -8
- package/crates/tish_eval/src/eval.rs +389 -142
- package/crates/tish_eval/src/lib.rs +10 -6
- package/crates/tish_eval/src/natives.rs +95 -38
- package/crates/tish_eval/src/promise.rs +14 -8
- package/crates/tish_eval/src/timers.rs +28 -19
- package/crates/tish_eval/src/value.rs +10 -3
- package/crates/tish_fmt/src/lib.rs +29 -13
- package/crates/tish_lexer/src/lib.rs +217 -63
- package/crates/tish_lexer/src/token.rs +6 -6
- package/crates/tish_llvm/src/lib.rs +15 -8
- package/crates/tish_lsp/src/main.rs +41 -43
- package/crates/tish_native/src/build.rs +1 -6
- package/crates/tish_native/src/lib.rs +48 -19
- package/crates/tish_opt/src/lib.rs +67 -50
- package/crates/tish_parser/src/lib.rs +36 -11
- package/crates/tish_parser/src/parser.rs +172 -87
- package/crates/tish_runtime/src/http.rs +15 -6
- package/crates/tish_runtime/src/http_fetch.rs +24 -14
- package/crates/tish_runtime/src/lib.rs +224 -168
- package/crates/tish_runtime/src/promise.rs +1 -5
- package/crates/tish_runtime/src/ws.rs +45 -20
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
- package/crates/tish_ui/src/jsx.rs +41 -22
- package/crates/tish_ui/src/lib.rs +2 -2
- package/crates/tish_vm/src/vm.rs +309 -112
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +8 -3
- package/crates/tish_wasm/src/lib.rs +38 -28
- package/crates/tishlang_cargo_bindgen/Cargo.toml +25 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +52 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -22,7 +22,9 @@ pub struct InferCtx {
|
|
|
22
22
|
|
|
23
23
|
impl InferCtx {
|
|
24
24
|
pub fn new() -> Self {
|
|
25
|
-
Self {
|
|
25
|
+
Self {
|
|
26
|
+
scopes: vec![HashMap::new()],
|
|
27
|
+
}
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
fn push_scope(&mut self) {
|
|
@@ -75,17 +77,16 @@ pub fn infer_expr_type(expr: &Expr, ctx: &InferCtx) -> Option<TypeAnnotation> {
|
|
|
75
77
|
Literal::Null => None,
|
|
76
78
|
},
|
|
77
79
|
Expr::Ident { name, .. } => ctx.lookup(name.as_ref()).cloned(),
|
|
78
|
-
Expr::Binary {
|
|
80
|
+
Expr::Binary {
|
|
81
|
+
left, op, right, ..
|
|
82
|
+
} => {
|
|
79
83
|
let lt = infer_expr_type(left, ctx)?;
|
|
80
84
|
let rt = infer_expr_type(right, ctx)?;
|
|
81
85
|
if is_number(<) && is_number(&rt) {
|
|
82
86
|
match op {
|
|
83
|
-
BinOp::Add
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
| BinOp::Div
|
|
87
|
-
| BinOp::Mod
|
|
88
|
-
| BinOp::Pow => Some(number_ann()),
|
|
87
|
+
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod | BinOp::Pow => {
|
|
88
|
+
Some(number_ann())
|
|
89
|
+
}
|
|
89
90
|
BinOp::Lt
|
|
90
91
|
| BinOp::Le
|
|
91
92
|
| BinOp::Gt
|
|
@@ -103,7 +104,11 @@ pub fn infer_expr_type(expr: &Expr, ctx: &InferCtx) -> Option<TypeAnnotation> {
|
|
|
103
104
|
match op {
|
|
104
105
|
UnaryOp::Neg | UnaryOp::Pos => {
|
|
105
106
|
let t = infer_expr_type(operand, ctx)?;
|
|
106
|
-
if is_number(&t) {
|
|
107
|
+
if is_number(&t) {
|
|
108
|
+
Some(number_ann())
|
|
109
|
+
} else {
|
|
110
|
+
None
|
|
111
|
+
}
|
|
107
112
|
}
|
|
108
113
|
UnaryOp::Not => Some(bool_ann()),
|
|
109
114
|
_ => None,
|
|
@@ -117,7 +122,9 @@ pub fn infer_expr_type(expr: &Expr, ctx: &InferCtx) -> Option<TypeAnnotation> {
|
|
|
117
122
|
/// type annotations filled in on `VarDecl` nodes.
|
|
118
123
|
pub fn infer_program(program: &Program) -> Program {
|
|
119
124
|
let mut ctx = InferCtx::new();
|
|
120
|
-
Program {
|
|
125
|
+
Program {
|
|
126
|
+
statements: infer_statements(&program.statements, &mut ctx),
|
|
127
|
+
}
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
fn infer_statements(stmts: &[Statement], ctx: &mut InferCtx) -> Vec<Statement> {
|
|
@@ -126,7 +133,13 @@ fn infer_statements(stmts: &[Statement], ctx: &mut InferCtx) -> Vec<Statement> {
|
|
|
126
133
|
|
|
127
134
|
fn infer_statement(stmt: &Statement, ctx: &mut InferCtx) -> Statement {
|
|
128
135
|
match stmt {
|
|
129
|
-
Statement::VarDecl {
|
|
136
|
+
Statement::VarDecl {
|
|
137
|
+
name,
|
|
138
|
+
mutable,
|
|
139
|
+
type_ann,
|
|
140
|
+
init,
|
|
141
|
+
span,
|
|
142
|
+
} => {
|
|
130
143
|
// Already annotated — propagate into ctx but don't change the node.
|
|
131
144
|
if let Some(ann) = type_ann {
|
|
132
145
|
ctx.define(name.as_ref(), ann.clone());
|
|
@@ -149,9 +162,18 @@ fn infer_statement(stmt: &Statement, ctx: &mut InferCtx) -> Statement {
|
|
|
149
162
|
ctx.push_scope();
|
|
150
163
|
let stmts = infer_statements(statements, ctx);
|
|
151
164
|
ctx.pop_scope();
|
|
152
|
-
Statement::Block {
|
|
165
|
+
Statement::Block {
|
|
166
|
+
statements: stmts,
|
|
167
|
+
span: *span,
|
|
168
|
+
}
|
|
153
169
|
}
|
|
154
|
-
Statement::For {
|
|
170
|
+
Statement::For {
|
|
171
|
+
init,
|
|
172
|
+
cond,
|
|
173
|
+
update,
|
|
174
|
+
body,
|
|
175
|
+
span,
|
|
176
|
+
} => {
|
|
155
177
|
// Scope for loop variable
|
|
156
178
|
ctx.push_scope();
|
|
157
179
|
let new_init = init.as_ref().map(|i| Box::new(infer_statement(i, ctx)));
|
|
@@ -165,7 +187,12 @@ fn infer_statement(stmt: &Statement, ctx: &mut InferCtx) -> Statement {
|
|
|
165
187
|
span: *span,
|
|
166
188
|
}
|
|
167
189
|
}
|
|
168
|
-
Statement::ForOf {
|
|
190
|
+
Statement::ForOf {
|
|
191
|
+
name,
|
|
192
|
+
iterable,
|
|
193
|
+
body,
|
|
194
|
+
span,
|
|
195
|
+
} => {
|
|
169
196
|
ctx.push_scope();
|
|
170
197
|
let new_body = Box::new(infer_statement(body, ctx));
|
|
171
198
|
ctx.pop_scope();
|
|
@@ -180,17 +207,32 @@ fn infer_statement(stmt: &Statement, ctx: &mut InferCtx) -> Statement {
|
|
|
180
207
|
ctx.push_scope();
|
|
181
208
|
let new_body = Box::new(infer_statement(body, ctx));
|
|
182
209
|
ctx.pop_scope();
|
|
183
|
-
Statement::While {
|
|
210
|
+
Statement::While {
|
|
211
|
+
cond: cond.clone(),
|
|
212
|
+
body: new_body,
|
|
213
|
+
span: *span,
|
|
214
|
+
}
|
|
184
215
|
}
|
|
185
216
|
Statement::DoWhile { body, cond, span } => {
|
|
186
217
|
ctx.push_scope();
|
|
187
218
|
let new_body = Box::new(infer_statement(body, ctx));
|
|
188
219
|
ctx.pop_scope();
|
|
189
|
-
Statement::DoWhile {
|
|
220
|
+
Statement::DoWhile {
|
|
221
|
+
body: new_body,
|
|
222
|
+
cond: cond.clone(),
|
|
223
|
+
span: *span,
|
|
224
|
+
}
|
|
190
225
|
}
|
|
191
|
-
Statement::If {
|
|
226
|
+
Statement::If {
|
|
227
|
+
cond,
|
|
228
|
+
then_branch,
|
|
229
|
+
else_branch,
|
|
230
|
+
span,
|
|
231
|
+
} => {
|
|
192
232
|
let new_then = Box::new(infer_statement(then_branch, ctx));
|
|
193
|
-
let new_else = else_branch
|
|
233
|
+
let new_else = else_branch
|
|
234
|
+
.as_ref()
|
|
235
|
+
.map(|e| Box::new(infer_statement(e, ctx)));
|
|
194
236
|
Statement::If {
|
|
195
237
|
cond: cond.clone(),
|
|
196
238
|
then_branch: new_then,
|
|
@@ -198,7 +240,15 @@ fn infer_statement(stmt: &Statement, ctx: &mut InferCtx) -> Statement {
|
|
|
198
240
|
span: *span,
|
|
199
241
|
}
|
|
200
242
|
}
|
|
201
|
-
Statement::FunDecl {
|
|
243
|
+
Statement::FunDecl {
|
|
244
|
+
async_,
|
|
245
|
+
name,
|
|
246
|
+
params,
|
|
247
|
+
rest_param,
|
|
248
|
+
return_type,
|
|
249
|
+
body,
|
|
250
|
+
span,
|
|
251
|
+
} => {
|
|
202
252
|
ctx.push_scope();
|
|
203
253
|
for p in params {
|
|
204
254
|
if let FunParam::Simple(tp) = p {
|
|
@@ -7,11 +7,11 @@ mod infer;
|
|
|
7
7
|
mod resolve;
|
|
8
8
|
mod types;
|
|
9
9
|
|
|
10
|
+
pub use codegen::CompileError;
|
|
10
11
|
pub use codegen::{
|
|
11
12
|
compile, compile_project, compile_project_full, compile_with_features,
|
|
12
13
|
compile_with_native_modules, compile_with_project_root,
|
|
13
14
|
};
|
|
14
|
-
pub use codegen::CompileError;
|
|
15
15
|
pub use resolve::{
|
|
16
16
|
cargo_export_fn_name, compute_native_build_artifacts, detect_cycles, export_name_to_rust_ident,
|
|
17
17
|
extract_native_import_features, format_rust_dependencies_toml, generate_native_wrapper_rs,
|
|
@@ -44,7 +44,10 @@ fn sum(...args: number[]): number {
|
|
|
44
44
|
// total should be declared as f64
|
|
45
45
|
assert!(rust.contains("let mut total: f64"), "expected total: f64");
|
|
46
46
|
// The return value of run() should convert total back to Value
|
|
47
|
-
assert!(
|
|
47
|
+
assert!(
|
|
48
|
+
rust.contains("Value::Number(total)"),
|
|
49
|
+
"expected Value::Number(total) wrapping"
|
|
50
|
+
);
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
#[test]
|
|
@@ -60,7 +63,10 @@ for (let i = 0; i < 5; i = i + 1) {
|
|
|
60
63
|
let program = parse(src).unwrap();
|
|
61
64
|
let rust = compile(&program).unwrap();
|
|
62
65
|
// outerVar and x are f64 (inferred) — Copy assignment, no .clone() needed.
|
|
63
|
-
assert!(
|
|
66
|
+
assert!(
|
|
67
|
+
rust.contains("let mut outerVar: f64"),
|
|
68
|
+
"expected outerVar: f64"
|
|
69
|
+
);
|
|
64
70
|
assert!(rust.contains("let mut x: f64"), "expected x: f64");
|
|
65
71
|
}
|
|
66
72
|
|
|
@@ -105,13 +111,17 @@ fn factory() {
|
|
|
105
111
|
// This test verifies the full benchmark_granular project compiles and that outerVar
|
|
106
112
|
// is emitted as the inferred f64 type rather than requiring a Value clone.
|
|
107
113
|
let manifest = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
108
|
-
let bench = manifest
|
|
114
|
+
let bench = manifest
|
|
115
|
+
.join("../../tests/core/benchmark_granular.tish")
|
|
116
|
+
.canonicalize()
|
|
117
|
+
.unwrap();
|
|
109
118
|
// Use same default features as tish CLI (http, fs, process, regex)
|
|
110
119
|
let features = ["http", "fs", "process", "regex"]
|
|
111
120
|
.into_iter()
|
|
112
121
|
.map(String::from)
|
|
113
122
|
.collect::<Vec<_>>();
|
|
114
|
-
let (rust, _, _, _) =
|
|
123
|
+
let (rust, _, _, _) =
|
|
124
|
+
compile_project_full(&bench, bench.parent(), &features, true).unwrap();
|
|
115
125
|
// outerVar = 42 is inferred as f64; f64 is Copy so no .clone() is emitted.
|
|
116
126
|
assert!(
|
|
117
127
|
rust.contains("let mut outerVar: f64"),
|
|
@@ -73,7 +73,10 @@ pub fn is_builtin_native_spec(spec: &str) -> bool {
|
|
|
73
73
|
/// Resolve all native imports in a merged program via package.json lookup.
|
|
74
74
|
/// Built-in modules (tish:fs, tish:http, tish:process) are skipped - they use tishlang_runtime directly.
|
|
75
75
|
/// Handles both lowered `NativeModuleLoad` (merged modules) and raw `import { … } from 'tish:…'`.
|
|
76
|
-
pub fn resolve_native_modules(
|
|
76
|
+
pub fn resolve_native_modules(
|
|
77
|
+
program: &Program,
|
|
78
|
+
project_root: &Path,
|
|
79
|
+
) -> Result<Vec<ResolvedNativeModule>, String> {
|
|
77
80
|
let root_canon = project_root
|
|
78
81
|
.canonicalize()
|
|
79
82
|
.map_err(|e| format!("Cannot canonicalize project root: {}", e))?;
|
|
@@ -131,12 +134,17 @@ pub fn cargo_export_fn_name(spec: &str) -> String {
|
|
|
131
134
|
out
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
fn resolve_cargo_native_module(
|
|
137
|
+
fn resolve_cargo_native_module(
|
|
138
|
+
spec: &str,
|
|
139
|
+
project_root: &Path,
|
|
140
|
+
) -> Result<ResolvedNativeModule, String> {
|
|
135
141
|
let tail = spec
|
|
136
142
|
.strip_prefix("cargo:")
|
|
137
143
|
.ok_or_else(|| format!("Invalid cargo native spec: {}", spec))?;
|
|
138
144
|
if tail.is_empty() {
|
|
139
|
-
return Err(
|
|
145
|
+
return Err(
|
|
146
|
+
"cargo: import needs a dependency name, e.g. import { x } from 'cargo:my_crate'".into(),
|
|
147
|
+
);
|
|
140
148
|
}
|
|
141
149
|
let dep_key = tail.to_string();
|
|
142
150
|
let tish = read_project_tish_config(project_root);
|
|
@@ -179,14 +187,26 @@ fn resolve_native_module(spec: &str, project_root: &Path) -> Result<ResolvedNati
|
|
|
179
187
|
let pkg_json = pkg_dir.join("package.json");
|
|
180
188
|
let content = std::fs::read_to_string(&pkg_json)
|
|
181
189
|
.map_err(|e| format!("Cannot read {}: {}", pkg_json.display(), e))?;
|
|
182
|
-
let json: serde_json::Value =
|
|
183
|
-
|
|
190
|
+
let json: serde_json::Value = serde_json::from_str(&content)
|
|
191
|
+
.map_err(|e| format!("Invalid JSON in {}: {}", pkg_json.display(), e))?;
|
|
184
192
|
let tish = json
|
|
185
193
|
.get("tish")
|
|
186
194
|
.and_then(|v| v.as_object())
|
|
187
|
-
.ok_or_else(||
|
|
188
|
-
|
|
189
|
-
|
|
195
|
+
.ok_or_else(|| {
|
|
196
|
+
format!(
|
|
197
|
+
"Package {} has no \"tish\" config in package.json",
|
|
198
|
+
package_name
|
|
199
|
+
)
|
|
200
|
+
})?;
|
|
201
|
+
if !tish
|
|
202
|
+
.get("module")
|
|
203
|
+
.and_then(|v| v.as_bool())
|
|
204
|
+
.unwrap_or(false)
|
|
205
|
+
{
|
|
206
|
+
return Err(format!(
|
|
207
|
+
"Package {} is not a Tish native module (tish.module must be true)",
|
|
208
|
+
package_name
|
|
209
|
+
));
|
|
190
210
|
}
|
|
191
211
|
let raw_crate = tish
|
|
192
212
|
.get("crate")
|
|
@@ -219,7 +239,9 @@ pub fn read_project_tish_config(project_root: &Path) -> serde_json::Value {
|
|
|
219
239
|
let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) else {
|
|
220
240
|
return serde_json::json!({});
|
|
221
241
|
};
|
|
222
|
-
json.get("tish")
|
|
242
|
+
json.get("tish")
|
|
243
|
+
.cloned()
|
|
244
|
+
.unwrap_or_else(|| serde_json::json!({}))
|
|
223
245
|
}
|
|
224
246
|
|
|
225
247
|
fn resolve_cargo_path_for_toml(project_root: &Path, raw: &str) -> String {
|
|
@@ -233,7 +255,10 @@ fn resolve_cargo_path_for_toml(project_root: &Path, raw: &str) -> String {
|
|
|
233
255
|
resolved.display().to_string().replace('\\', "/")
|
|
234
256
|
}
|
|
235
257
|
|
|
236
|
-
fn json_to_cargo_inline_value(
|
|
258
|
+
fn json_to_cargo_inline_value(
|
|
259
|
+
v: &serde_json::Value,
|
|
260
|
+
project_root: &Path,
|
|
261
|
+
) -> Result<String, String> {
|
|
237
262
|
match v {
|
|
238
263
|
serde_json::Value::String(s) => Ok(format!("{:?}", s.as_str())),
|
|
239
264
|
serde_json::Value::Bool(b) => Ok(b.to_string()),
|
|
@@ -264,7 +289,10 @@ fn json_to_cargo_inline_value(v: &serde_json::Value, project_root: &Path) -> Res
|
|
|
264
289
|
|
|
265
290
|
/// Serialize `tish.rustDependencies` from project `package.json` into Cargo.toml `[dependencies]` lines.
|
|
266
291
|
/// Relative `path = "…"` entries in inline tables are resolved against `project_root` so the temp build crate can find them.
|
|
267
|
-
pub fn format_rust_dependencies_toml(
|
|
292
|
+
pub fn format_rust_dependencies_toml(
|
|
293
|
+
tish: &serde_json::Value,
|
|
294
|
+
project_root: &Path,
|
|
295
|
+
) -> Result<String, String> {
|
|
268
296
|
let Some(obj) = tish.get("rustDependencies").and_then(|v| v.as_object()) else {
|
|
269
297
|
return Ok(String::new());
|
|
270
298
|
};
|
|
@@ -313,7 +341,10 @@ pub fn infer_native_module_exports(program: &Program) -> HashMap<String, HashSet
|
|
|
313
341
|
for stmt in &program.statements {
|
|
314
342
|
match stmt {
|
|
315
343
|
Statement::VarDecl {
|
|
316
|
-
init:
|
|
344
|
+
init:
|
|
345
|
+
Some(Expr::NativeModuleLoad {
|
|
346
|
+
spec, export_name, ..
|
|
347
|
+
}),
|
|
317
348
|
..
|
|
318
349
|
} => {
|
|
319
350
|
let s = spec.as_ref();
|
|
@@ -324,8 +355,11 @@ pub fn infer_native_module_exports(program: &Program) -> HashMap<String, HashSet
|
|
|
324
355
|
.or_default()
|
|
325
356
|
.insert(export_name.to_string());
|
|
326
357
|
}
|
|
327
|
-
Statement::Import {
|
|
328
|
-
|
|
358
|
+
Statement::Import {
|
|
359
|
+
specifiers, from, ..
|
|
360
|
+
} if is_native_import(from.as_ref()) => {
|
|
361
|
+
let spec =
|
|
362
|
+
normalize_builtin_spec(from.as_ref()).unwrap_or_else(|| from.to_string());
|
|
329
363
|
if is_builtin_native_spec(&spec) {
|
|
330
364
|
continue;
|
|
331
365
|
}
|
|
@@ -358,7 +392,11 @@ pub fn generate_native_wrapper_rs(
|
|
|
358
392
|
);
|
|
359
393
|
let mut any = false;
|
|
360
394
|
for m in modules {
|
|
361
|
-
let Some(NativeModuleInit::Generated {
|
|
395
|
+
let Some(NativeModuleInit::Generated {
|
|
396
|
+
shim_crate,
|
|
397
|
+
export_fn,
|
|
398
|
+
}) = init_by_spec.get(&m.spec)
|
|
399
|
+
else {
|
|
362
400
|
continue;
|
|
363
401
|
};
|
|
364
402
|
let Some(names) = inferred.get(&m.spec) else {
|
|
@@ -405,9 +443,16 @@ pub fn compute_native_build_artifacts(
|
|
|
405
443
|
let mut native_init: HashMap<String, NativeModuleInit> = HashMap::new();
|
|
406
444
|
for m in native_modules {
|
|
407
445
|
let use_gen = if is_cargo_native_spec(&m.spec) {
|
|
408
|
-
inferred
|
|
446
|
+
inferred
|
|
447
|
+
.get(&m.spec)
|
|
448
|
+
.map(|s| !s.is_empty())
|
|
449
|
+
.unwrap_or(false)
|
|
409
450
|
} else {
|
|
410
|
-
gen_tish
|
|
451
|
+
gen_tish
|
|
452
|
+
&& inferred
|
|
453
|
+
.get(&m.spec)
|
|
454
|
+
.map(|s| !s.is_empty())
|
|
455
|
+
.unwrap_or(false)
|
|
411
456
|
};
|
|
412
457
|
let init = if use_gen {
|
|
413
458
|
NativeModuleInit::Generated {
|
|
@@ -538,13 +583,18 @@ pub fn resolve_project(
|
|
|
538
583
|
entry_path: &Path,
|
|
539
584
|
project_root: Option<&Path>,
|
|
540
585
|
) -> Result<Vec<ResolvedModule>, String> {
|
|
541
|
-
let project_root =
|
|
586
|
+
let project_root =
|
|
587
|
+
project_root.unwrap_or_else(|| entry_path.parent().unwrap_or(Path::new(".")));
|
|
542
588
|
let entry_canon = entry_path
|
|
543
589
|
.canonicalize()
|
|
544
590
|
.map_err(|e| format!("Cannot canonicalize entry {}: {}", entry_path.display(), e))?;
|
|
545
|
-
let root_canon = project_root
|
|
546
|
-
|
|
547
|
-
|
|
591
|
+
let root_canon = project_root.canonicalize().map_err(|e| {
|
|
592
|
+
format!(
|
|
593
|
+
"Cannot canonicalize project root {}: {}",
|
|
594
|
+
project_root.display(),
|
|
595
|
+
e
|
|
596
|
+
)
|
|
597
|
+
})?;
|
|
548
598
|
|
|
549
599
|
let mut visited = HashSet::new();
|
|
550
600
|
let mut path_to_module: HashMap<PathBuf, Program> = HashMap::new();
|
|
@@ -574,21 +624,23 @@ pub fn resolve_project_from_stdin(
|
|
|
574
624
|
source: &str,
|
|
575
625
|
project_root: &Path,
|
|
576
626
|
) -> Result<Vec<ResolvedModule>, String> {
|
|
577
|
-
let root_canon = project_root
|
|
578
|
-
|
|
579
|
-
|
|
627
|
+
let root_canon = project_root.canonicalize().map_err(|e| {
|
|
628
|
+
format!(
|
|
629
|
+
"Cannot canonicalize project root {}: {}",
|
|
630
|
+
project_root.display(),
|
|
631
|
+
e
|
|
632
|
+
)
|
|
633
|
+
})?;
|
|
580
634
|
|
|
581
635
|
let stdin_path = root_canon.join("<stdin>");
|
|
582
|
-
let program =
|
|
583
|
-
.map_err(|e| format!("Parse error (stdin): {}", e))?;
|
|
636
|
+
let program =
|
|
637
|
+
tishlang_parser::parse(source).map_err(|e| format!("Parse error (stdin): {}", e))?;
|
|
584
638
|
|
|
585
639
|
let mut visited = HashSet::new();
|
|
586
640
|
let mut path_to_module: HashMap<PathBuf, Program> = HashMap::new();
|
|
587
641
|
let mut load_order: Vec<PathBuf> = Vec::new();
|
|
588
642
|
|
|
589
|
-
let from_dir = stdin_path
|
|
590
|
-
.parent()
|
|
591
|
-
.unwrap_or_else(|| Path::new("."));
|
|
643
|
+
let from_dir = stdin_path.parent().unwrap_or_else(|| Path::new("."));
|
|
592
644
|
|
|
593
645
|
for stmt in &program.statements {
|
|
594
646
|
if let Statement::Import { from, .. } = stmt {
|
|
@@ -787,7 +839,10 @@ pub fn detect_cycles(modules: &[ResolvedModule]) -> Result<(), String> {
|
|
|
787
839
|
.iter()
|
|
788
840
|
.map(|&i| modules[i].path.display().to_string())
|
|
789
841
|
.collect();
|
|
790
|
-
return Err(format!(
|
|
842
|
+
return Err(format!(
|
|
843
|
+
"Circular import detected: {}",
|
|
844
|
+
path_names.join(" -> ")
|
|
845
|
+
));
|
|
791
846
|
}
|
|
792
847
|
}
|
|
793
848
|
Ok(())
|
|
@@ -817,14 +872,8 @@ fn has_cycle_from(
|
|
|
817
872
|
stack.push(dep_idx);
|
|
818
873
|
let dep = &modules[dep_idx];
|
|
819
874
|
let dep_dir = dep.path.parent().unwrap_or(Path::new("."));
|
|
820
|
-
if has_cycle_from(
|
|
821
|
-
|
|
822
|
-
&dep.program,
|
|
823
|
-
path_to_idx,
|
|
824
|
-
modules,
|
|
825
|
-
stack,
|
|
826
|
-
visiting,
|
|
827
|
-
)? {
|
|
875
|
+
if has_cycle_from(dep_dir, &dep.program, path_to_idx, modules, stack, visiting)?
|
|
876
|
+
{
|
|
828
877
|
return Ok(true);
|
|
829
878
|
}
|
|
830
879
|
stack.pop();
|
|
@@ -874,12 +923,15 @@ pub fn merge_modules(modules: Vec<ResolvedModule>) -> Result<Program, String> {
|
|
|
874
923
|
let dir = module.path.parent().unwrap_or(Path::new("."));
|
|
875
924
|
for stmt in &module.program.statements {
|
|
876
925
|
match stmt {
|
|
877
|
-
Statement::Import {
|
|
926
|
+
Statement::Import {
|
|
927
|
+
specifiers,
|
|
928
|
+
from,
|
|
929
|
+
span,
|
|
930
|
+
} => {
|
|
878
931
|
if is_native_import(from.as_ref()) {
|
|
879
932
|
// Normalize fs/http/process -> tish:fs etc. for Node compatibility
|
|
880
|
-
let canonical_spec =
|
|
881
|
-
|
|
882
|
-
.unwrap_or_else(|| from.to_string());
|
|
933
|
+
let canonical_spec = normalize_builtin_spec(from.as_ref())
|
|
934
|
+
.unwrap_or_else(|| from.to_string());
|
|
883
935
|
// Emit VarDecl with NativeModuleLoad for each specifier
|
|
884
936
|
for spec in specifiers {
|
|
885
937
|
match spec {
|
|
@@ -918,9 +970,7 @@ pub fn merge_modules(modules: Vec<ResolvedModule>) -> Result<Program, String> {
|
|
|
918
970
|
continue;
|
|
919
971
|
}
|
|
920
972
|
let dep_path = resolve_import_path(from.as_ref(), dir, Path::new("."))?;
|
|
921
|
-
let dep_path = dep_path
|
|
922
|
-
.canonicalize()
|
|
923
|
-
.unwrap_or(dep_path);
|
|
973
|
+
let dep_path = dep_path.canonicalize().unwrap_or(dep_path);
|
|
924
974
|
let dep_idx = *path_to_idx
|
|
925
975
|
.get(&dep_path)
|
|
926
976
|
.ok_or_else(|| format!("Resolved import '{}' not in module list", from))?;
|
|
@@ -961,18 +1011,13 @@ pub fn merge_modules(modules: Vec<ResolvedModule>) -> Result<Program, String> {
|
|
|
961
1011
|
name: ns.clone(),
|
|
962
1012
|
mutable: false,
|
|
963
1013
|
type_ann: None,
|
|
964
|
-
init: Some(Expr::Object {
|
|
965
|
-
props,
|
|
966
|
-
span: *span,
|
|
967
|
-
}),
|
|
1014
|
+
init: Some(Expr::Object { props, span: *span }),
|
|
968
1015
|
span: *span,
|
|
969
1016
|
});
|
|
970
1017
|
}
|
|
971
1018
|
ImportSpecifier::Default(bind) => {
|
|
972
|
-
let source =
|
|
973
|
-
.get("default")
|
|
974
|
-
.cloned()
|
|
975
|
-
.ok_or_else(|| {
|
|
1019
|
+
let source =
|
|
1020
|
+
dep_exports.get("default").cloned().ok_or_else(|| {
|
|
976
1021
|
format!("Module '{}' has no default export", from)
|
|
977
1022
|
})?;
|
|
978
1023
|
statements.push(Statement::VarDecl {
|
|
@@ -989,21 +1034,19 @@ pub fn merge_modules(modules: Vec<ResolvedModule>) -> Result<Program, String> {
|
|
|
989
1034
|
}
|
|
990
1035
|
}
|
|
991
1036
|
}
|
|
992
|
-
Statement::Export { declaration, .. } => {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
});
|
|
1004
|
-
}
|
|
1037
|
+
Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
1038
|
+
ExportDeclaration::Named(s) => statements.push(*s.clone()),
|
|
1039
|
+
ExportDeclaration::Default(e) => {
|
|
1040
|
+
let default_name = format!("__default_{}", idx);
|
|
1041
|
+
statements.push(Statement::VarDecl {
|
|
1042
|
+
name: Arc::from(default_name),
|
|
1043
|
+
mutable: false,
|
|
1044
|
+
type_ann: None,
|
|
1045
|
+
init: Some((*e).clone()),
|
|
1046
|
+
span: e.span(),
|
|
1047
|
+
});
|
|
1005
1048
|
}
|
|
1006
|
-
}
|
|
1049
|
+
},
|
|
1007
1050
|
_ => statements.push(stmt.clone()),
|
|
1008
1051
|
}
|
|
1009
1052
|
}
|
|
@@ -1051,8 +1094,8 @@ mod cargo_spec_tests {
|
|
|
1051
1094
|
#[test]
|
|
1052
1095
|
fn cargo_export_fn_name_sanitizes() {
|
|
1053
1096
|
assert_eq!(
|
|
1054
|
-
cargo_export_fn_name("cargo:
|
|
1055
|
-
"
|
|
1097
|
+
cargo_export_fn_name("cargo:tish_serde_json"),
|
|
1098
|
+
"cargo_native_tish_serde_json_object"
|
|
1056
1099
|
);
|
|
1057
1100
|
assert_eq!(
|
|
1058
1101
|
cargo_export_fn_name("cargo:my-crate"),
|
|
@@ -45,9 +45,7 @@ impl RustType {
|
|
|
45
45
|
"any" => RustType::Value,
|
|
46
46
|
_ => RustType::Value, // Unknown types fall back to Value
|
|
47
47
|
},
|
|
48
|
-
TypeAnnotation::Array(elem) =>
|
|
49
|
-
RustType::Vec(Box::new(Self::from_annotation(elem)))
|
|
50
|
-
}
|
|
48
|
+
TypeAnnotation::Array(elem) => RustType::Vec(Box::new(Self::from_annotation(elem))),
|
|
51
49
|
TypeAnnotation::Object(fields) => {
|
|
52
50
|
let typed_fields: Vec<_> = fields
|
|
53
51
|
.iter()
|
|
@@ -66,13 +64,13 @@ impl RustType {
|
|
|
66
64
|
TypeAnnotation::Union(types) => {
|
|
67
65
|
// Check for T | null pattern -> Option<T>
|
|
68
66
|
if types.len() == 2 {
|
|
69
|
-
let has_null = types
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
let has_null = types
|
|
68
|
+
.iter()
|
|
69
|
+
.any(|t| matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null"));
|
|
72
70
|
if has_null {
|
|
73
|
-
let non_null = types.iter().find(
|
|
74
|
-
!matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null")
|
|
75
|
-
|
|
71
|
+
let non_null = types.iter().find(
|
|
72
|
+
|t| !matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null"),
|
|
73
|
+
);
|
|
76
74
|
if let Some(inner) = non_null {
|
|
77
75
|
return RustType::Option(Box::new(Self::from_annotation(inner)));
|
|
78
76
|
}
|
|
@@ -271,9 +269,7 @@ impl TypeContext {
|
|
|
271
269
|
|
|
272
270
|
/// Check if a variable is typed (has a non-Value type).
|
|
273
271
|
pub fn is_typed(&self, name: &str) -> bool {
|
|
274
|
-
self.lookup(name)
|
|
275
|
-
.map(|ty| ty.is_native())
|
|
276
|
-
.unwrap_or(false)
|
|
272
|
+
self.lookup(name).map(|ty| ty.is_native()).unwrap_or(false)
|
|
277
273
|
}
|
|
278
274
|
|
|
279
275
|
/// Get the type of a variable, defaulting to Value if not found.
|
|
@@ -329,12 +325,12 @@ mod tests {
|
|
|
329
325
|
ctx.define("x", RustType::F64);
|
|
330
326
|
assert_eq!(ctx.get_type("x"), RustType::F64);
|
|
331
327
|
assert!(ctx.is_typed("x"));
|
|
332
|
-
|
|
328
|
+
|
|
333
329
|
ctx.push_scope();
|
|
334
330
|
ctx.define("y", RustType::String);
|
|
335
331
|
assert_eq!(ctx.get_type("y"), RustType::String);
|
|
336
332
|
assert_eq!(ctx.get_type("x"), RustType::F64); // Can still see outer scope
|
|
337
|
-
|
|
333
|
+
|
|
338
334
|
ctx.pop_scope();
|
|
339
335
|
assert_eq!(ctx.get_type("y"), RustType::Value); // y no longer visible
|
|
340
336
|
}
|