@tishlang/tish 1.9.1 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +8 -6
- package/crates/js_to_tish/src/transform/stmt.rs +12 -13
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/cargo_native_registry.rs +4 -1
- package/crates/tish/src/main.rs +11 -8
- package/crates/tish/tests/integration_test.rs +145 -7
- package/crates/tish_ast/src/ast.rs +3 -9
- package/crates/tish_build_utils/src/lib.rs +43 -15
- package/crates/tish_builtins/src/array.rs +2 -3
- package/crates/tish_builtins/src/construct.rs +15 -28
- package/crates/tish_builtins/src/globals.rs +18 -16
- package/crates/tish_builtins/src/helpers.rs +1 -4
- package/crates/tish_builtins/src/lib.rs +1 -0
- package/crates/tish_builtins/src/object.rs +10 -10
- package/crates/tish_builtins/src/string.rs +1 -3
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_compile/src/codegen.rs +123 -138
- package/crates/tish_compile/src/lib.rs +25 -3
- package/crates/tish_compile/src/resolve.rs +6 -3
- package/crates/tish_compile/src/types.rs +6 -6
- package/crates/tish_compile_js/src/codegen.rs +50 -29
- package/crates/tish_compile_js/src/tests_jsx.rs +44 -0
- package/crates/tish_core/src/console_style.rs +9 -0
- package/crates/tish_core/src/json.rs +17 -7
- package/crates/tish_core/src/macros.rs +2 -2
- package/crates/tish_core/src/value.rs +192 -4
- package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
- package/crates/tish_eval/src/eval.rs +135 -73
- package/crates/tish_eval/src/http.rs +18 -12
- package/crates/tish_eval/src/lib.rs +29 -0
- package/crates/tish_eval/src/regex.rs +1 -1
- package/crates/tish_eval/src/value.rs +89 -4
- package/crates/tish_eval/src/value_convert.rs +30 -8
- package/crates/tish_fmt/src/lib.rs +4 -1
- package/crates/tish_lexer/src/lib.rs +7 -2
- package/crates/tish_llvm/src/lib.rs +2 -2
- package/crates/tish_lsp/src/builtin_goto.rs +111 -10
- package/crates/tish_lsp/src/import_goto.rs +35 -22
- package/crates/tish_lsp/src/main.rs +118 -85
- package/crates/tish_native/src/build.rs +187 -10
- package/crates/tish_native/src/lib.rs +92 -8
- package/crates/tish_parser/src/lib.rs +77 -0
- package/crates/tish_parser/src/parser.rs +71 -74
- package/crates/tish_pg/src/error.rs +1 -1
- package/crates/tish_pg/src/lib.rs +61 -73
- package/crates/tish_resolve/src/lib.rs +283 -158
- package/crates/tish_resolve/src/pos.rs +10 -2
- package/crates/tish_runtime/Cargo.toml +3 -0
- package/crates/tish_runtime/src/http.rs +39 -39
- package/crates/tish_runtime/src/http_fetch.rs +12 -12
- package/crates/tish_runtime/src/lib.rs +26 -43
- package/crates/tish_runtime/src/native_promise.rs +0 -11
- package/crates/tish_runtime/src/promise.rs +14 -1
- package/crates/tish_runtime/src/promise_io.rs +1 -4
- package/crates/tish_runtime/src/ws.rs +40 -27
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
- package/crates/tish_ui/src/jsx.rs +6 -4
- package/crates/tish_ui/src/lib.rs +2 -2
- package/crates/tish_ui/src/runtime/hooks.rs +5 -15
- package/crates/tish_ui/src/runtime/mod.rs +16 -17
- package/crates/tish_vm/Cargo.toml +2 -0
- package/crates/tish_vm/src/vm.rs +218 -153
- package/crates/tish_wasm/src/lib.rs +33 -7
- package/crates/tish_wasm_runtime/Cargo.toml +4 -1
- package/crates/tish_wasm_runtime/src/lib.rs +2 -1
- package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
- package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
- package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
- package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
- package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
- package/justfile +3 -3
- 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
|
@@ -14,6 +14,22 @@ mod build;
|
|
|
14
14
|
use std::path::Path;
|
|
15
15
|
use tishlang_ast::Program;
|
|
16
16
|
|
|
17
|
+
/// Features for the embeddable VM (`cranelift` / `llvm` backends): merge CLI capability flags
|
|
18
|
+
/// (same set as `tish run` / `tish build --target native`) with features inferred from
|
|
19
|
+
/// `import … from 'tish:…'`. Programs that use globals only (`Promise`, `fetch`, `setTimeout`)
|
|
20
|
+
/// still need the former so [`tishlang_cranelift_runtime`] links `tishlang_vm` with matching gates.
|
|
21
|
+
fn merged_embed_runtime_features(cli: &[String], program: &Program) -> Vec<String> {
|
|
22
|
+
let mut out = cli.to_vec();
|
|
23
|
+
for f in tishlang_compile::extract_native_import_features(program) {
|
|
24
|
+
if !out.contains(&f) {
|
|
25
|
+
out.push(f);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
out.sort();
|
|
29
|
+
out.dedup();
|
|
30
|
+
out
|
|
31
|
+
}
|
|
32
|
+
|
|
17
33
|
/// Error from native compilation.
|
|
18
34
|
#[derive(Debug)]
|
|
19
35
|
pub struct NativeError {
|
|
@@ -93,8 +109,8 @@ pub fn compile_to_native(
|
|
|
93
109
|
let prog = tishlang_compile::merge_modules(modules)
|
|
94
110
|
.map(|m| m.program)
|
|
95
111
|
.map_err(|e| NativeError {
|
|
96
|
-
|
|
97
|
-
|
|
112
|
+
message: e.to_string(),
|
|
113
|
+
})?;
|
|
98
114
|
if optimize {
|
|
99
115
|
tishlang_opt::optimize(&prog)
|
|
100
116
|
} else {
|
|
@@ -118,7 +134,7 @@ pub fn compile_to_native(
|
|
|
118
134
|
})?
|
|
119
135
|
};
|
|
120
136
|
|
|
121
|
-
let cranelift_features =
|
|
137
|
+
let cranelift_features = merged_embed_runtime_features(features, &program);
|
|
122
138
|
tishlang_cranelift::compile_chunk_to_native(&chunk, output_path, &cranelift_features)
|
|
123
139
|
.map_err(|e| NativeError {
|
|
124
140
|
message: e.to_string(),
|
|
@@ -138,8 +154,8 @@ pub fn compile_to_native(
|
|
|
138
154
|
let prog = tishlang_compile::merge_modules(modules)
|
|
139
155
|
.map(|m| m.program)
|
|
140
156
|
.map_err(|e| NativeError {
|
|
141
|
-
|
|
142
|
-
|
|
157
|
+
message: e.to_string(),
|
|
158
|
+
})?;
|
|
143
159
|
if optimize {
|
|
144
160
|
tishlang_opt::optimize(&prog)
|
|
145
161
|
} else {
|
|
@@ -160,7 +176,7 @@ pub fn compile_to_native(
|
|
|
160
176
|
message: e.to_string(),
|
|
161
177
|
})?
|
|
162
178
|
};
|
|
163
|
-
let llvm_features =
|
|
179
|
+
let llvm_features = merged_embed_runtime_features(features, &program);
|
|
164
180
|
tishlang_llvm::compile_chunk_to_native(&chunk, output_path, &llvm_features)
|
|
165
181
|
.map_err(|e| NativeError { message: e.message })
|
|
166
182
|
}
|
|
@@ -249,7 +265,7 @@ pub fn compile_program_to_native(
|
|
|
249
265
|
message: e.to_string(),
|
|
250
266
|
})?
|
|
251
267
|
};
|
|
252
|
-
let cranelift_features =
|
|
268
|
+
let cranelift_features = merged_embed_runtime_features(features, &program);
|
|
253
269
|
tishlang_cranelift::compile_chunk_to_native(&chunk, output_path, &cranelift_features)
|
|
254
270
|
.map_err(|e| NativeError {
|
|
255
271
|
message: e.to_string(),
|
|
@@ -275,13 +291,81 @@ pub fn compile_program_to_native(
|
|
|
275
291
|
message: e.to_string(),
|
|
276
292
|
})?
|
|
277
293
|
};
|
|
278
|
-
let llvm_features =
|
|
294
|
+
let llvm_features = merged_embed_runtime_features(features, &program);
|
|
279
295
|
tishlang_llvm::compile_chunk_to_native(&chunk, output_path, &llvm_features)
|
|
280
296
|
.map_err(|e| NativeError { message: e.message })
|
|
281
297
|
}
|
|
282
298
|
}
|
|
283
299
|
}
|
|
284
300
|
|
|
301
|
+
/// Compile multiple entry `.tish` files to native binaries in **one** nested Cargo build.
|
|
302
|
+
///
|
|
303
|
+
/// Intended for integration tests and batch tooling; keeps production [`compile_to_native`] behavior
|
|
304
|
+
/// unchanged when `TISH_FAST_NATIVE_BUILD` is unset.
|
|
305
|
+
pub fn compile_many_to_native(
|
|
306
|
+
entries: &[(&Path, &Path)],
|
|
307
|
+
project_root: Option<&Path>,
|
|
308
|
+
features: &[String],
|
|
309
|
+
optimize: bool,
|
|
310
|
+
) -> Result<(), NativeError> {
|
|
311
|
+
let mut bins: Vec<(String, String, Option<String>)> = Vec::with_capacity(entries.len());
|
|
312
|
+
let mut merged_native_modules: Vec<tishlang_compile::ResolvedNativeModule> = Vec::new();
|
|
313
|
+
let mut merged_features: Vec<String> = features.to_vec();
|
|
314
|
+
let mut merged_extra_deps = String::new();
|
|
315
|
+
let mut needs_tokio = false;
|
|
316
|
+
let mut needs_ui = false;
|
|
317
|
+
|
|
318
|
+
for (entry_path, _) in entries {
|
|
319
|
+
let (rust_code, native_modules, effective_features, native_build) =
|
|
320
|
+
tishlang_compile::compile_project_full(entry_path, project_root, features, optimize)
|
|
321
|
+
.map_err(|e| NativeError {
|
|
322
|
+
message: e.to_string(),
|
|
323
|
+
})?;
|
|
324
|
+
let stem = entry_path
|
|
325
|
+
.file_stem()
|
|
326
|
+
.and_then(|s| s.to_str())
|
|
327
|
+
.ok_or_else(|| NativeError {
|
|
328
|
+
message: format!("invalid entry path: {}", entry_path.display()),
|
|
329
|
+
})?
|
|
330
|
+
.to_string();
|
|
331
|
+
|
|
332
|
+
for f in &effective_features {
|
|
333
|
+
if !merged_features.contains(f) {
|
|
334
|
+
merged_features.push(f.clone());
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
for m in native_modules {
|
|
338
|
+
let dup = merged_native_modules
|
|
339
|
+
.iter()
|
|
340
|
+
.any(|x| x.package_name == m.package_name && x.crate_path == m.crate_path);
|
|
341
|
+
if !dup {
|
|
342
|
+
merged_native_modules.push(m);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
let extra = native_build.rust_dependencies_toml.trim();
|
|
346
|
+
if !extra.is_empty() {
|
|
347
|
+
merged_extra_deps.push_str(extra);
|
|
348
|
+
merged_extra_deps.push('\n');
|
|
349
|
+
}
|
|
350
|
+
needs_tokio |= rust_code.contains("#[tokio::main]");
|
|
351
|
+
needs_ui |= rust_code.contains("tishlang_ui");
|
|
352
|
+
bins.push((stem, rust_code, native_build.generated_native_rs));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let merged_extra = merged_extra_deps.trim();
|
|
356
|
+
crate::build::build_many_via_cargo(
|
|
357
|
+
bins,
|
|
358
|
+
merged_native_modules,
|
|
359
|
+
&merged_features,
|
|
360
|
+
merged_extra,
|
|
361
|
+
needs_tokio,
|
|
362
|
+
needs_ui,
|
|
363
|
+
entries,
|
|
364
|
+
project_root,
|
|
365
|
+
)
|
|
366
|
+
.map_err(|e| NativeError { message: e })
|
|
367
|
+
}
|
|
368
|
+
|
|
285
369
|
enum Backend {
|
|
286
370
|
Rust,
|
|
287
371
|
Cranelift,
|
|
@@ -252,4 +252,81 @@ mod tests {
|
|
|
252
252
|
parse(SRC).expect("stdlib/builtins.d.tish should parse");
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
#[test]
|
|
256
|
+
fn for_empty_head_parses() {
|
|
257
|
+
let src = r#"fn f() {
|
|
258
|
+
for (;;)
|
|
259
|
+
const x = 1
|
|
260
|
+
}"#;
|
|
261
|
+
let program = parse(src).expect("for (;;)");
|
|
262
|
+
let body = match &program.statements[0] {
|
|
263
|
+
Statement::FunDecl { body, .. } => body,
|
|
264
|
+
_ => panic!("expected fn"),
|
|
265
|
+
};
|
|
266
|
+
let stmts = match body.as_ref() {
|
|
267
|
+
Statement::Block { statements, .. } => statements,
|
|
268
|
+
_ => panic!("expected block body"),
|
|
269
|
+
};
|
|
270
|
+
assert!(
|
|
271
|
+
matches!(
|
|
272
|
+
stmts.iter().find(|s| matches!(s, Statement::For { .. })),
|
|
273
|
+
Some(Statement::For {
|
|
274
|
+
init: None,
|
|
275
|
+
cond: None,
|
|
276
|
+
update: None,
|
|
277
|
+
..
|
|
278
|
+
})
|
|
279
|
+
),
|
|
280
|
+
"expected for (;;)"
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#[test]
|
|
285
|
+
fn brace_function_body_does_not_nest_block_around_first_let() {
|
|
286
|
+
let src = "fn h() {\n let a = 1\n let b = 2\n}\n";
|
|
287
|
+
let program = parse(src).expect("parse");
|
|
288
|
+
let body = match &program.statements[0] {
|
|
289
|
+
Statement::FunDecl { body, .. } => body,
|
|
290
|
+
_ => panic!("expected fn"),
|
|
291
|
+
};
|
|
292
|
+
let stmts = match body.as_ref() {
|
|
293
|
+
Statement::Block { statements, .. } => statements,
|
|
294
|
+
_ => panic!("expected block body"),
|
|
295
|
+
};
|
|
296
|
+
assert_eq!(
|
|
297
|
+
stmts.len(),
|
|
298
|
+
2,
|
|
299
|
+
"expected two top-level lets in fn body, not Block(let) + let — got {stmts:?}"
|
|
300
|
+
);
|
|
301
|
+
assert!(matches!(stmts[0], Statement::VarDecl { .. }));
|
|
302
|
+
assert!(matches!(stmts[1], Statement::VarDecl { .. }));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#[test]
|
|
306
|
+
fn member_access_allows_type_property_name() {
|
|
307
|
+
let src = "fn f() {\n const label = 0\n label.type = \"button\"\n}\n";
|
|
308
|
+
parse(src).expect("label.type should parse: `type` is a keyword but valid after `.`");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#[test]
|
|
312
|
+
fn brace_block_stmt_then_const_then_if_are_siblings() {
|
|
313
|
+
let src = "fn g() {\n f()\n const x = 1\n if (x) {\n f()\n }\n}\n";
|
|
314
|
+
let program = parse(src).expect("parse");
|
|
315
|
+
let body = match &program.statements[0] {
|
|
316
|
+
Statement::FunDecl { body, .. } => body,
|
|
317
|
+
_ => panic!("expected fn"),
|
|
318
|
+
};
|
|
319
|
+
let stmts = match body.as_ref() {
|
|
320
|
+
Statement::Block { statements, .. } => statements,
|
|
321
|
+
_ => panic!("expected block body"),
|
|
322
|
+
};
|
|
323
|
+
assert_eq!(
|
|
324
|
+
stmts.len(),
|
|
325
|
+
3,
|
|
326
|
+
"expected expr; const; if as siblings — got {stmts:?}"
|
|
327
|
+
);
|
|
328
|
+
assert!(matches!(stmts[0], Statement::ExprStmt { .. }));
|
|
329
|
+
assert!(matches!(stmts[1], Statement::VarDecl { .. }));
|
|
330
|
+
assert!(matches!(stmts[2], Statement::If { .. }));
|
|
331
|
+
}
|
|
255
332
|
}
|
|
@@ -99,6 +99,18 @@ impl<'a> Parser<'a> {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/// After `.` / `?.`, allow `type` as a member name (`TokenKind::Type`); see `docs/js-emit-philosophy.md`.
|
|
103
|
+
fn expect_ident_or_type_member_name(&mut self) -> Result<&Token, String> {
|
|
104
|
+
match self.peek_kind() {
|
|
105
|
+
Some(TokenKind::Ident) => self.expect(TokenKind::Ident),
|
|
106
|
+
Some(TokenKind::Type) => self.expect(TokenKind::Type),
|
|
107
|
+
other => Err(format!(
|
|
108
|
+
"Expected property name after `.` or `?.`, got {:?}",
|
|
109
|
+
other
|
|
110
|
+
)),
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
102
114
|
fn span_end(&self, start: (usize, usize)) -> Span {
|
|
103
115
|
let end = self.peek().map(|t| t.span.start).unwrap_or(start);
|
|
104
116
|
Span { start, end }
|
|
@@ -194,8 +206,17 @@ impl<'a> Parser<'a> {
|
|
|
194
206
|
fn parse_block(&mut self) -> Result<Statement, String> {
|
|
195
207
|
let span_start = self.peek().ok_or("Unexpected EOF")?.span.start;
|
|
196
208
|
|
|
197
|
-
|
|
209
|
+
let opened_with_brace = matches!(self.peek_kind(), Some(TokenKind::LBrace));
|
|
210
|
+
if opened_with_brace {
|
|
198
211
|
self.advance(); // {
|
|
212
|
+
// After `{`, the lexer often emits `Indent` for the first indented line of the body.
|
|
213
|
+
// `parse_statement` treats a leading `Indent` as starting a *nested* indent-block, so
|
|
214
|
+
// without consuming this token we get `Block { Block { let ... } ; ... }` and the first
|
|
215
|
+
// `let`/`const` is scoped too narrowly (JS ReferenceError). This indent is layout for
|
|
216
|
+
// *this* brace block, not an inner block.
|
|
217
|
+
if matches!(self.peek_kind(), Some(TokenKind::Indent)) {
|
|
218
|
+
self.advance();
|
|
219
|
+
}
|
|
199
220
|
} else if matches!(self.peek_kind(), Some(TokenKind::Indent)) {
|
|
200
221
|
self.advance(); // Indent
|
|
201
222
|
}
|
|
@@ -270,10 +291,7 @@ impl<'a> Parser<'a> {
|
|
|
270
291
|
start: name_tok.span.start,
|
|
271
292
|
end: name_tok.span.end,
|
|
272
293
|
};
|
|
273
|
-
let name = name_tok
|
|
274
|
-
.literal
|
|
275
|
-
.clone()
|
|
276
|
-
.ok_or("Expected identifier")?;
|
|
294
|
+
let name = name_tok.literal.clone().ok_or("Expected identifier")?;
|
|
277
295
|
|
|
278
296
|
// Optional type annotation: `: Type`
|
|
279
297
|
let type_ann = if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
|
|
@@ -327,10 +345,7 @@ impl<'a> Parser<'a> {
|
|
|
327
345
|
start: name_tok.span.start,
|
|
328
346
|
end: name_tok.span.end,
|
|
329
347
|
};
|
|
330
|
-
let name = name_tok
|
|
331
|
-
.literal
|
|
332
|
-
.clone()
|
|
333
|
-
.ok_or("Expected identifier")?;
|
|
348
|
+
let name = name_tok.literal.clone().ok_or("Expected identifier")?;
|
|
334
349
|
elements.push(Some(DestructElement::Rest(name, name_span)));
|
|
335
350
|
break;
|
|
336
351
|
}
|
|
@@ -347,10 +362,7 @@ impl<'a> Parser<'a> {
|
|
|
347
362
|
start: name_tok.span.start,
|
|
348
363
|
end: name_tok.span.end,
|
|
349
364
|
};
|
|
350
|
-
let name = name_tok
|
|
351
|
-
.literal
|
|
352
|
-
.clone()
|
|
353
|
-
.ok_or("Expected identifier")?;
|
|
365
|
+
let name = name_tok.literal.clone().ok_or("Expected identifier")?;
|
|
354
366
|
DestructElement::Ident(name, name_span)
|
|
355
367
|
}
|
|
356
368
|
_ => return Err("Expected identifier or pattern in destructuring".to_string()),
|
|
@@ -378,10 +390,7 @@ impl<'a> Parser<'a> {
|
|
|
378
390
|
start: key_tok.span.start,
|
|
379
391
|
end: key_tok.span.end,
|
|
380
392
|
};
|
|
381
|
-
let key = key_tok
|
|
382
|
-
.literal
|
|
383
|
-
.clone()
|
|
384
|
-
.ok_or("Expected identifier")?;
|
|
393
|
+
let key = key_tok.literal.clone().ok_or("Expected identifier")?;
|
|
385
394
|
|
|
386
395
|
let value = if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
|
|
387
396
|
self.advance();
|
|
@@ -397,10 +406,7 @@ impl<'a> Parser<'a> {
|
|
|
397
406
|
start: name_tok.span.start,
|
|
398
407
|
end: name_tok.span.end,
|
|
399
408
|
};
|
|
400
|
-
let name = name_tok
|
|
401
|
-
.literal
|
|
402
|
-
.clone()
|
|
403
|
-
.ok_or("Expected identifier")?;
|
|
409
|
+
let name = name_tok.literal.clone().ok_or("Expected identifier")?;
|
|
404
410
|
DestructElement::Ident(name, name_span)
|
|
405
411
|
}
|
|
406
412
|
_ => return Err("Expected identifier or pattern after ':'".to_string()),
|
|
@@ -453,10 +459,7 @@ impl<'a> Parser<'a> {
|
|
|
453
459
|
start: param_tok.span.start,
|
|
454
460
|
end: param_tok.span.end,
|
|
455
461
|
};
|
|
456
|
-
let param_name = param_tok
|
|
457
|
-
.literal
|
|
458
|
-
.clone()
|
|
459
|
-
.ok_or("Expected param name")?;
|
|
462
|
+
let param_name = param_tok.literal.clone().ok_or("Expected param name")?;
|
|
460
463
|
let type_ann = if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
|
|
461
464
|
self.advance();
|
|
462
465
|
Some(self.parse_type_annotation()?)
|
|
@@ -528,11 +531,27 @@ impl<'a> Parser<'a> {
|
|
|
528
531
|
self.advance(); // {
|
|
529
532
|
let mut props = Vec::new();
|
|
530
533
|
while !matches!(self.peek_kind(), Some(TokenKind::RBrace)) {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
534
|
+
// `for` is a keyword but a common method name (`Symbol.for`); allow it here.
|
|
535
|
+
let key: Arc<str> = match self.peek_kind() {
|
|
536
|
+
Some(TokenKind::Ident) => {
|
|
537
|
+
let tok = self.expect(TokenKind::Ident)?;
|
|
538
|
+
Arc::from(
|
|
539
|
+
tok.literal
|
|
540
|
+
.as_deref()
|
|
541
|
+
.ok_or("Expected property name")?,
|
|
542
|
+
)
|
|
543
|
+
}
|
|
544
|
+
Some(TokenKind::For) => {
|
|
545
|
+
self.advance();
|
|
546
|
+
Arc::from("for")
|
|
547
|
+
}
|
|
548
|
+
_ => {
|
|
549
|
+
return Err(format!(
|
|
550
|
+
"Expected Ident or `for` as object type property name, got {:?}",
|
|
551
|
+
self.peek_kind()
|
|
552
|
+
));
|
|
553
|
+
}
|
|
554
|
+
};
|
|
536
555
|
self.expect(TokenKind::Colon)?;
|
|
537
556
|
let typ = self.parse_type_annotation()?;
|
|
538
557
|
props.push((key, typ));
|
|
@@ -580,10 +599,7 @@ impl<'a> Parser<'a> {
|
|
|
580
599
|
start: name_tok.span.start,
|
|
581
600
|
end: name_tok.span.end,
|
|
582
601
|
};
|
|
583
|
-
let name = name_tok
|
|
584
|
-
.literal
|
|
585
|
-
.clone()
|
|
586
|
-
.ok_or("Expected function name")?;
|
|
602
|
+
let name = name_tok.literal.clone().ok_or("Expected function name")?;
|
|
587
603
|
self.expect(TokenKind::LParen)?;
|
|
588
604
|
let mut params = Vec::with_capacity(4);
|
|
589
605
|
let mut rest_param = None;
|
|
@@ -595,10 +611,7 @@ impl<'a> Parser<'a> {
|
|
|
595
611
|
start: rest_tok.span.start,
|
|
596
612
|
end: rest_tok.span.end,
|
|
597
613
|
};
|
|
598
|
-
let param_name = rest_tok
|
|
599
|
-
.literal
|
|
600
|
-
.clone()
|
|
601
|
-
.ok_or("Expected rest param name")?;
|
|
614
|
+
let param_name = rest_tok.literal.clone().ok_or("Expected rest param name")?;
|
|
602
615
|
// Optional type annotation for rest param
|
|
603
616
|
let type_ann = if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
|
|
604
617
|
self.advance();
|
|
@@ -681,10 +694,7 @@ impl<'a> Parser<'a> {
|
|
|
681
694
|
start: name_tok.span.start,
|
|
682
695
|
end: name_tok.span.end,
|
|
683
696
|
};
|
|
684
|
-
let name = name_tok
|
|
685
|
-
.literal
|
|
686
|
-
.clone()
|
|
687
|
-
.ok_or("Expected type alias name")?;
|
|
697
|
+
let name = name_tok.literal.clone().ok_or("Expected type alias name")?;
|
|
688
698
|
self.expect(TokenKind::Assign)?;
|
|
689
699
|
let ty = self.parse_type_annotation()?;
|
|
690
700
|
Ok(Statement::TypeAlias {
|
|
@@ -726,10 +736,7 @@ impl<'a> Parser<'a> {
|
|
|
726
736
|
start: name_tok.span.start,
|
|
727
737
|
end: name_tok.span.end,
|
|
728
738
|
};
|
|
729
|
-
let name = name_tok
|
|
730
|
-
.literal
|
|
731
|
-
.clone()
|
|
732
|
-
.ok_or("Expected identifier")?;
|
|
739
|
+
let name = name_tok.literal.clone().ok_or("Expected identifier")?;
|
|
733
740
|
let type_ann = if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
|
|
734
741
|
self.advance();
|
|
735
742
|
Some(self.parse_type_annotation()?)
|
|
@@ -759,10 +766,7 @@ impl<'a> Parser<'a> {
|
|
|
759
766
|
start: name_tok.span.start,
|
|
760
767
|
end: name_tok.span.end,
|
|
761
768
|
};
|
|
762
|
-
let name = name_tok
|
|
763
|
-
.literal
|
|
764
|
-
.clone()
|
|
765
|
-
.ok_or("Expected function name")?;
|
|
769
|
+
let name = name_tok.literal.clone().ok_or("Expected function name")?;
|
|
766
770
|
self.expect(TokenKind::LParen)?;
|
|
767
771
|
let mut params = Vec::with_capacity(4);
|
|
768
772
|
let mut rest_param = None;
|
|
@@ -774,10 +778,7 @@ impl<'a> Parser<'a> {
|
|
|
774
778
|
start: rest_tok.span.start,
|
|
775
779
|
end: rest_tok.span.end,
|
|
776
780
|
};
|
|
777
|
-
let param_name = rest_tok
|
|
778
|
-
.literal
|
|
779
|
-
.clone()
|
|
780
|
-
.ok_or("Expected rest param name")?;
|
|
781
|
+
let param_name = rest_tok.literal.clone().ok_or("Expected rest param name")?;
|
|
781
782
|
let type_ann = if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
|
|
782
783
|
self.advance();
|
|
783
784
|
Some(self.parse_type_annotation()?)
|
|
@@ -872,10 +873,7 @@ impl<'a> Parser<'a> {
|
|
|
872
873
|
start: for_name_tok.span.start,
|
|
873
874
|
end: for_name_tok.span.end,
|
|
874
875
|
};
|
|
875
|
-
let name = for_name_tok
|
|
876
|
-
.literal
|
|
877
|
-
.clone()
|
|
878
|
-
.ok_or("Expected identifier")?;
|
|
876
|
+
let name = for_name_tok.literal.clone().ok_or("Expected identifier")?;
|
|
879
877
|
if matches!(self.peek_kind(), Some(TokenKind::Of)) {
|
|
880
878
|
self.advance();
|
|
881
879
|
let iterable = self.parse_expr()?;
|
|
@@ -938,6 +936,11 @@ impl<'a> Parser<'a> {
|
|
|
938
936
|
self.expect(TokenKind::Semicolon)?;
|
|
939
937
|
Some(c)
|
|
940
938
|
};
|
|
939
|
+
// `for (init; ; update)` — when the condition is empty we matched `;` above but did not
|
|
940
|
+
// consume it; skip it so `update` / `)` parse correctly (e.g. `for (;;)`).
|
|
941
|
+
if cond.is_none() && matches!(self.peek_kind(), Some(TokenKind::Semicolon)) {
|
|
942
|
+
self.advance();
|
|
943
|
+
}
|
|
941
944
|
let update = if matches!(self.peek_kind(), Some(TokenKind::RParen)) {
|
|
942
945
|
None
|
|
943
946
|
} else {
|
|
@@ -1160,10 +1163,7 @@ impl<'a> Parser<'a> {
|
|
|
1160
1163
|
start: def_tok.span.start,
|
|
1161
1164
|
end: def_tok.span.end,
|
|
1162
1165
|
};
|
|
1163
|
-
let name = def_tok
|
|
1164
|
-
.literal
|
|
1165
|
-
.clone()
|
|
1166
|
-
.ok_or("Expected identifier")?;
|
|
1166
|
+
let name = def_tok.literal.clone().ok_or("Expected identifier")?;
|
|
1167
1167
|
vec![ImportSpecifier::Default { name, name_span }]
|
|
1168
1168
|
} else {
|
|
1169
1169
|
return Err("Expected { }, * as name, or default import".to_string());
|
|
@@ -1245,7 +1245,9 @@ impl<'a> Parser<'a> {
|
|
|
1245
1245
|
// Member assignment: obj.prop = val
|
|
1246
1246
|
if let Expr::Member {
|
|
1247
1247
|
object,
|
|
1248
|
-
prop: MemberProp::Name {
|
|
1248
|
+
prop: MemberProp::Name {
|
|
1249
|
+
name: prop_name, ..
|
|
1250
|
+
},
|
|
1249
1251
|
..
|
|
1250
1252
|
} = &left
|
|
1251
1253
|
{
|
|
@@ -1483,11 +1485,8 @@ impl<'a> Parser<'a> {
|
|
|
1483
1485
|
TokenKind::Dot | TokenKind::OptionalChain => {
|
|
1484
1486
|
let optional = kind == TokenKind::OptionalChain;
|
|
1485
1487
|
self.advance();
|
|
1486
|
-
let prop_tok = self.
|
|
1487
|
-
let prop = prop_tok
|
|
1488
|
-
.literal
|
|
1489
|
-
.clone()
|
|
1490
|
-
.ok_or("Expected property name")?;
|
|
1488
|
+
let prop_tok = self.expect_ident_or_type_member_name()?;
|
|
1489
|
+
let prop = prop_tok.literal.clone().ok_or("Expected property name")?;
|
|
1491
1490
|
let prop_span = Span {
|
|
1492
1491
|
start: prop_tok.span.start,
|
|
1493
1492
|
end: prop_tok.span.end,
|
|
@@ -1603,11 +1602,8 @@ impl<'a> Parser<'a> {
|
|
|
1603
1602
|
TokenKind::Dot | TokenKind::OptionalChain => {
|
|
1604
1603
|
let optional = kind == TokenKind::OptionalChain;
|
|
1605
1604
|
self.advance();
|
|
1606
|
-
let prop_tok = self.
|
|
1607
|
-
let prop = prop_tok
|
|
1608
|
-
.literal
|
|
1609
|
-
.clone()
|
|
1610
|
-
.ok_or("Expected property name")?;
|
|
1605
|
+
let prop_tok = self.expect_ident_or_type_member_name()?;
|
|
1606
|
+
let prop = prop_tok.literal.clone().ok_or("Expected property name")?;
|
|
1611
1607
|
let prop_span = Span {
|
|
1612
1608
|
start: prop_tok.span.start,
|
|
1613
1609
|
end: prop_tok.span.end,
|
|
@@ -1997,7 +1993,8 @@ impl<'a> Parser<'a> {
|
|
|
1997
1993
|
self.expect(TokenKind::RBrace)?; // }
|
|
1998
1994
|
props.push(JsxProp::Spread(expr));
|
|
1999
1995
|
}
|
|
2000
|
-
|
|
1996
|
+
// `type` is `TokenKind::Type` but valid as a JSX attr name; see docs/js-emit-philosophy.md.
|
|
1997
|
+
Some(TokenKind::Ident) | Some(TokenKind::Type) => {
|
|
2001
1998
|
let name_tok = self.advance().unwrap();
|
|
2002
1999
|
let name = name_tok.literal.clone().ok_or("Expected attr name")?;
|
|
2003
2000
|
if matches!(self.peek_kind(), Some(TokenKind::Assign)) {
|