@tishlang/tish-format 2.16.13 → 2.35.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-format +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +12 -2
- package/crates/tish/tests/fixtures/fs_parity_callbacks.tish +22 -0
- package/crates/tish/tests/fixtures/fs_parity_promises.tish +21 -0
- package/crates/tish/tests/fixtures/fs_parity_sync.tish +20 -0
- package/crates/tish/tests/fs_parity.rs +103 -0
- package/crates/tish/tests/shortcircuit.rs +3 -3
- package/crates/tish_ast/src/ast.rs +77 -3
- package/crates/tish_builtins/Cargo.toml +1 -0
- package/crates/tish_builtins/src/array.rs +195 -16
- package/crates/tish_builtins/src/collections.rs +37 -2
- package/crates/tish_builtins/src/globals.rs +215 -25
- package/crates/tish_builtins/src/math.rs +65 -7
- package/crates/tish_builtins/src/number.rs +173 -0
- package/crates/tish_builtins/src/string.rs +168 -15
- package/crates/tish_bytecode/src/compiler.rs +56 -4
- package/crates/tish_bytecode/src/opcode.rs +4 -2
- package/crates/tish_compile/src/codegen.rs +513 -44
- package/crates/tish_compile/src/infer.rs +26 -11
- package/crates/tish_compile/src/resolve.rs +87 -10
- package/crates/tish_compile_js/src/codegen.rs +82 -24
- package/crates/tish_compile_js/src/tests_jsx.rs +27 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +17 -2
- package/crates/tish_core/src/json.rs +5 -1
- package/crates/tish_core/src/lib.rs +48 -2
- package/crates/tish_core/src/uri.rs +45 -0
- package/crates/tish_core/src/value.rs +6 -0
- package/crates/tish_eval/Cargo.toml +2 -1
- package/crates/tish_eval/src/eval.rs +655 -27
- package/crates/tish_eval/src/natives.rs +264 -18
- package/crates/tish_eval/src/regex.rs +47 -1
- package/crates/tish_fmt/Cargo.toml +1 -1
- package/crates/tish_fmt/src/lib.rs +23 -4
- package/crates/tish_opt/src/lib.rs +20 -1
- package/crates/tish_parser/src/lib.rs +41 -0
- package/crates/tish_parser/src/parser.rs +180 -11
- package/crates/tish_resolve/src/lib.rs +66 -5
- package/crates/tish_runtime/Cargo.toml +5 -1
- package/crates/tish_runtime/src/fs_ext.rs +359 -0
- package/crates/tish_runtime/src/http_hyper.rs +41 -18
- package/crates/tish_runtime/src/lib.rs +128 -9
- package/crates/tish_ui/src/jsx.rs +18 -3
- package/crates/tish_vm/src/jit.rs +1 -1
- package/crates/tish_vm/src/vm.rs +270 -60
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- package/platform/win32-x64/tish-fmt.exe +0 -0
|
@@ -1290,6 +1290,21 @@ impl<'a> Parser<'a> {
|
|
|
1290
1290
|
span: self.span_end(span_start),
|
|
1291
1291
|
});
|
|
1292
1292
|
}
|
|
1293
|
+
// `for (let k in obj)` — after a declared loop variable, `in` is for-in enumeration, not
|
|
1294
|
+
// the binary `in` operator (#413). Iterates the object's own enumerable keys.
|
|
1295
|
+
if matches!(self.peek_kind(), Some(TokenKind::In)) {
|
|
1296
|
+
self.advance();
|
|
1297
|
+
let object = self.parse_expr()?;
|
|
1298
|
+
self.expect(TokenKind::RParen)?;
|
|
1299
|
+
let body = Box::new(self.parse_block_or_statement()?);
|
|
1300
|
+
return Ok(Statement::ForIn {
|
|
1301
|
+
name,
|
|
1302
|
+
name_span,
|
|
1303
|
+
object,
|
|
1304
|
+
body,
|
|
1305
|
+
span: self.span_end(span_start),
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1293
1308
|
let type_ann = if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
|
|
1294
1309
|
self.advance();
|
|
1295
1310
|
Some(self.parse_type_annotation()?)
|
|
@@ -1667,7 +1682,15 @@ impl<'a> Parser<'a> {
|
|
|
1667
1682
|
}
|
|
1668
1683
|
}
|
|
1669
1684
|
self.expect(TokenKind::RBrace)?;
|
|
1670
|
-
|
|
1685
|
+
// #415: the `from` clause is OPTIONAL. `export { a, b as c } from "./m"` re-exports;
|
|
1686
|
+
// `export { a, b as c }` (no `from`) is a LOCAL named export of already-declared bindings.
|
|
1687
|
+
let from = if matches!(self.peek_kind(), Some(TokenKind::Ident))
|
|
1688
|
+
&& self.peek().and_then(|t| t.literal.as_deref()) == Some("from")
|
|
1689
|
+
{
|
|
1690
|
+
Some(self.parse_from_clause()?)
|
|
1691
|
+
} else {
|
|
1692
|
+
None
|
|
1693
|
+
};
|
|
1671
1694
|
ExportDeclaration::ReExport {
|
|
1672
1695
|
specifiers,
|
|
1673
1696
|
all: false,
|
|
@@ -1675,9 +1698,9 @@ impl<'a> Parser<'a> {
|
|
|
1675
1698
|
span: self.span_end(span_start),
|
|
1676
1699
|
}
|
|
1677
1700
|
} else if matches!(self.peek_kind(), Some(TokenKind::Star)) {
|
|
1678
|
-
// #305: re-export all — `export * from "./m"`
|
|
1701
|
+
// #305: re-export all — `export * from "./m"` (always requires `from`).
|
|
1679
1702
|
self.advance(); // *
|
|
1680
|
-
let from = self.parse_from_clause()
|
|
1703
|
+
let from = Some(self.parse_from_clause()?);
|
|
1681
1704
|
ExportDeclaration::ReExport {
|
|
1682
1705
|
specifiers: Vec::new(),
|
|
1683
1706
|
all: true,
|
|
@@ -1977,9 +2000,54 @@ impl<'a> Parser<'a> {
|
|
|
1977
2000
|
binary_single_op!(parse_bit_and, parse_shift, BitAnd, BinOp::BitAnd);
|
|
1978
2001
|
|
|
1979
2002
|
binary_multi_op!(parse_shift, parse_equality, Shl => BinOp::Shl, Shr => BinOp::Shr, UShr => BinOp::UShr);
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2003
|
+
|
|
2004
|
+
// Loose equality `==` / `!=` is rejected at parse, uniformly across every backend (#244). Its
|
|
2005
|
+
// coercion is a footgun, and leaving it accepted was a portability trap: interp/vm silently
|
|
2006
|
+
// treated `==` as strict, the `--target js` backend emitted true JS loose `==` (so `1 == "1"`
|
|
2007
|
+
// disagreed), and the native-rust backend refused to compile it. Only `===` / `!==` are accepted;
|
|
2008
|
+
// `Eq` / `Ne` produce an actionable error pointing at the strict form. `StrictEq` / `StrictNe`
|
|
2009
|
+
// parse exactly as the generic `binary_multi_op!` would.
|
|
2010
|
+
fn parse_equality(&mut self) -> Result<Expr, String> {
|
|
2011
|
+
let mut left = self.parse_comparison()?;
|
|
2012
|
+
loop {
|
|
2013
|
+
let op = match self.peek_kind() {
|
|
2014
|
+
Some(TokenKind::StrictEq) => BinOp::StrictEq,
|
|
2015
|
+
Some(TokenKind::StrictNe) => BinOp::StrictNe,
|
|
2016
|
+
Some(TokenKind::Eq) => {
|
|
2017
|
+
let at = self
|
|
2018
|
+
.peek()
|
|
2019
|
+
.map(|t| format!("{:?}", t.span))
|
|
2020
|
+
.unwrap_or_else(|| "EOF".to_string());
|
|
2021
|
+
return Err(format!(
|
|
2022
|
+
"Loose equality '==' is not supported — use strict '===' (at {})",
|
|
2023
|
+
at
|
|
2024
|
+
));
|
|
2025
|
+
}
|
|
2026
|
+
Some(TokenKind::Ne) => {
|
|
2027
|
+
let at = self
|
|
2028
|
+
.peek()
|
|
2029
|
+
.map(|t| format!("{:?}", t.span))
|
|
2030
|
+
.unwrap_or_else(|| "EOF".to_string());
|
|
2031
|
+
return Err(format!(
|
|
2032
|
+
"Loose inequality '!=' is not supported — use strict '!==' (at {})",
|
|
2033
|
+
at
|
|
2034
|
+
));
|
|
2035
|
+
}
|
|
2036
|
+
_ => break,
|
|
2037
|
+
};
|
|
2038
|
+
self.advance();
|
|
2039
|
+
let right = self.parse_comparison()?;
|
|
2040
|
+
let start = expr_span(&left).start;
|
|
2041
|
+
let end = expr_span(&right).end;
|
|
2042
|
+
left = Expr::Binary {
|
|
2043
|
+
left: Box::new(left),
|
|
2044
|
+
op,
|
|
2045
|
+
right: Box::new(right),
|
|
2046
|
+
span: Span { start, end },
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
Ok(left)
|
|
2050
|
+
}
|
|
1983
2051
|
binary_multi_op!(parse_comparison, parse_term,
|
|
1984
2052
|
Lt => BinOp::Lt, Le => BinOp::Le, Gt => BinOp::Gt, Ge => BinOp::Ge, In => BinOp::In);
|
|
1985
2053
|
binary_multi_op!(parse_term, parse_factor, Plus => BinOp::Add, Minus => BinOp::Sub);
|
|
@@ -2396,17 +2464,71 @@ impl<'a> Parser<'a> {
|
|
|
2396
2464
|
value: Literal::Null,
|
|
2397
2465
|
span,
|
|
2398
2466
|
}),
|
|
2399
|
-
// `
|
|
2400
|
-
// `
|
|
2401
|
-
//
|
|
2467
|
+
// `async` in expression position (#428): an async arrow — `async () => …` or
|
|
2468
|
+
// `async x => …`. The body runs in async context (`await` allowed). Async function
|
|
2469
|
+
// expressions / method shorthand need function-expression support, which tish lacks.
|
|
2470
|
+
TokenKind::Async => {
|
|
2471
|
+
if matches!(self.peek_kind(), Some(TokenKind::LParen)) {
|
|
2472
|
+
self.advance(); // consume `(`
|
|
2473
|
+
if let Some(arrow) = self.try_parse_arrow_function(&span, true)? {
|
|
2474
|
+
return Ok(arrow);
|
|
2475
|
+
}
|
|
2476
|
+
return Err("Expected an arrow function after `async (`".to_string());
|
|
2477
|
+
}
|
|
2478
|
+
if matches!(
|
|
2479
|
+
self.peek_kind(),
|
|
2480
|
+
Some(TokenKind::Ident | TokenKind::Fn | TokenKind::Type)
|
|
2481
|
+
) {
|
|
2482
|
+
let name_tok = self.advance().ok_or("Expected parameter name")?;
|
|
2483
|
+
let name = name_tok.literal.clone().ok_or("Expected parameter name")?;
|
|
2484
|
+
let name_span = Span {
|
|
2485
|
+
start: name_tok.span.start,
|
|
2486
|
+
end: name_tok.span.end,
|
|
2487
|
+
};
|
|
2488
|
+
self.expect(TokenKind::Arrow)?;
|
|
2489
|
+
let body = self.parse_arrow_body()?;
|
|
2490
|
+
let end = self.previous_span_end();
|
|
2491
|
+
return Ok(Expr::ArrowFunction {
|
|
2492
|
+
async_: true,
|
|
2493
|
+
params: vec![FunParam::Simple(TypedParam {
|
|
2494
|
+
name,
|
|
2495
|
+
name_span,
|
|
2496
|
+
type_ann: None,
|
|
2497
|
+
default: None,
|
|
2498
|
+
})],
|
|
2499
|
+
body,
|
|
2500
|
+
span: Span {
|
|
2501
|
+
start: span.start,
|
|
2502
|
+
end,
|
|
2503
|
+
},
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
Err(
|
|
2507
|
+
"`async` in expression position must be followed by an arrow function (`async () => …`)"
|
|
2508
|
+
.to_string(),
|
|
2509
|
+
)
|
|
2510
|
+
}
|
|
2511
|
+
// `fn` / `type` as a value reference or single-param arrow head (issue #55), OR a function
|
|
2512
|
+
// EXPRESSION `function (…) {…}` / named `function f(…) {…}` (#464). statement-leading
|
|
2513
|
+
// `function`/`type` decls are handled in parse_statement.
|
|
2402
2514
|
TokenKind::Ident | TokenKind::Fn | TokenKind::Type => {
|
|
2403
2515
|
let name = t.literal.clone().ok_or("Expected ident")?;
|
|
2516
|
+
let is_fn_kw = t.kind == TokenKind::Fn;
|
|
2517
|
+
// Function expression: `function` (`Fn`) followed by `(` (anonymous) or an identifier
|
|
2518
|
+
// (named) has no other valid meaning in value position. `t` is no longer read past
|
|
2519
|
+
// here, so borrowing `self` for the peek is sound.
|
|
2520
|
+
if is_fn_kw
|
|
2521
|
+
&& matches!(self.peek_kind(), Some(TokenKind::LParen | TokenKind::Ident))
|
|
2522
|
+
{
|
|
2523
|
+
return self.parse_function_expr_tail(&span, false);
|
|
2524
|
+
}
|
|
2404
2525
|
// Check if this is a single-param arrow function: x => ...
|
|
2405
2526
|
if matches!(self.peek_kind(), Some(TokenKind::Arrow)) {
|
|
2406
2527
|
self.advance(); // consume =>
|
|
2407
2528
|
let body = self.parse_arrow_body()?;
|
|
2408
2529
|
let end = self.previous_span_end();
|
|
2409
2530
|
return Ok(Expr::ArrowFunction {
|
|
2531
|
+
async_: false,
|
|
2410
2532
|
params: vec![FunParam::Simple(TypedParam {
|
|
2411
2533
|
name: name.clone(),
|
|
2412
2534
|
name_span: span,
|
|
@@ -2424,7 +2546,7 @@ impl<'a> Parser<'a> {
|
|
|
2424
2546
|
}
|
|
2425
2547
|
TokenKind::LParen => {
|
|
2426
2548
|
// Check if this is an arrow function: (params) => ...
|
|
2427
|
-
if let Some(arrow_fn) = self.try_parse_arrow_function(&span)? {
|
|
2549
|
+
if let Some(arrow_fn) = self.try_parse_arrow_function(&span, false)? {
|
|
2428
2550
|
return Ok(arrow_fn);
|
|
2429
2551
|
}
|
|
2430
2552
|
// Otherwise it's a grouping expression
|
|
@@ -2584,7 +2706,11 @@ impl<'a> Parser<'a> {
|
|
|
2584
2706
|
|
|
2585
2707
|
/// Try to parse an arrow function starting with '(' already consumed.
|
|
2586
2708
|
/// Returns Some(Expr) if successful, None if it's not an arrow function.
|
|
2587
|
-
fn try_parse_arrow_function(
|
|
2709
|
+
fn try_parse_arrow_function(
|
|
2710
|
+
&mut self,
|
|
2711
|
+
start_span: &Span,
|
|
2712
|
+
async_: bool,
|
|
2713
|
+
) -> Result<Option<Expr>, String> {
|
|
2588
2714
|
// Save position for backtracking
|
|
2589
2715
|
let saved_pos = self.pos;
|
|
2590
2716
|
|
|
@@ -2638,6 +2764,7 @@ impl<'a> Parser<'a> {
|
|
|
2638
2764
|
let end = self.previous_span_end();
|
|
2639
2765
|
|
|
2640
2766
|
Ok(Some(Expr::ArrowFunction {
|
|
2767
|
+
async_,
|
|
2641
2768
|
params,
|
|
2642
2769
|
body,
|
|
2643
2770
|
span: Span {
|
|
@@ -2647,6 +2774,48 @@ impl<'a> Parser<'a> {
|
|
|
2647
2774
|
}))
|
|
2648
2775
|
}
|
|
2649
2776
|
|
|
2777
|
+
/// Parse a `function` EXPRESSION after its `function` token has been consumed (#464):
|
|
2778
|
+
/// `[name] (params) [: RetType] { body }`. Lowers to an `ArrowFunction` with a block body so every
|
|
2779
|
+
/// backend reuses the existing arrow path. An optional name is parsed for source fidelity but not
|
|
2780
|
+
/// bound in scope, so a named function expression cannot yet self-reference by that name (a
|
|
2781
|
+
/// documented follow-up). `this`/`arguments` follow arrow semantics — fine for the common callback
|
|
2782
|
+
/// / IIFE / assigned-callback uses.
|
|
2783
|
+
fn parse_function_expr_tail(
|
|
2784
|
+
&mut self,
|
|
2785
|
+
start_span: &Span,
|
|
2786
|
+
async_: bool,
|
|
2787
|
+
) -> Result<Expr, String> {
|
|
2788
|
+
// Optional name (named function expression).
|
|
2789
|
+
if matches!(self.peek_kind(), Some(TokenKind::Ident)) {
|
|
2790
|
+
self.advance();
|
|
2791
|
+
}
|
|
2792
|
+
self.expect(TokenKind::LParen)?;
|
|
2793
|
+
let mut params = Vec::new();
|
|
2794
|
+
while !matches!(self.peek_kind(), Some(TokenKind::RParen)) {
|
|
2795
|
+
params.push(self.parse_fun_param()?);
|
|
2796
|
+
if !matches!(self.peek_kind(), Some(TokenKind::RParen)) {
|
|
2797
|
+
self.expect(TokenKind::Comma)?;
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
self.expect(TokenKind::RParen)?;
|
|
2801
|
+
// Optional return-type annotation (`: T`) — parsed and discarded (ArrowFunction carries none).
|
|
2802
|
+
if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
|
|
2803
|
+
self.advance();
|
|
2804
|
+
self.parse_type_annotation()?;
|
|
2805
|
+
}
|
|
2806
|
+
let block = self.parse_block()?;
|
|
2807
|
+
let end = self.previous_span_end();
|
|
2808
|
+
Ok(Expr::ArrowFunction {
|
|
2809
|
+
async_,
|
|
2810
|
+
params,
|
|
2811
|
+
body: ArrowBody::Block(Box::new(block)),
|
|
2812
|
+
span: Span {
|
|
2813
|
+
start: start_span.start,
|
|
2814
|
+
end,
|
|
2815
|
+
},
|
|
2816
|
+
})
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2650
2819
|
/// Parse the body of an arrow function (either expression or block)
|
|
2651
2820
|
fn parse_arrow_body(&mut self) -> Result<ArrowBody, String> {
|
|
2652
2821
|
if matches!(self.peek_kind(), Some(TokenKind::LBrace)) {
|
|
@@ -177,6 +177,13 @@ fn collect_stmt(
|
|
|
177
177
|
iterable,
|
|
178
178
|
body,
|
|
179
179
|
..
|
|
180
|
+
}
|
|
181
|
+
| Statement::ForIn {
|
|
182
|
+
name,
|
|
183
|
+
name_span,
|
|
184
|
+
object: iterable,
|
|
185
|
+
body,
|
|
186
|
+
..
|
|
180
187
|
} => {
|
|
181
188
|
consider(source, lsp_line, lsp_char, name_span, name.clone(), best);
|
|
182
189
|
collect_expr(iterable, source, lsp_line, lsp_char, best);
|
|
@@ -939,7 +946,12 @@ fn member_chain_collect_stmt(
|
|
|
939
946
|
}
|
|
940
947
|
member_chain_collect_stmt(body, source, lsp_line, lsp_char, best);
|
|
941
948
|
}
|
|
942
|
-
Statement::ForOf { iterable, body, .. }
|
|
949
|
+
Statement::ForOf { iterable, body, .. }
|
|
950
|
+
| Statement::ForIn {
|
|
951
|
+
object: iterable,
|
|
952
|
+
body,
|
|
953
|
+
..
|
|
954
|
+
} => {
|
|
943
955
|
member_chain_collect_expr(iterable, source, lsp_line, lsp_char, best);
|
|
944
956
|
member_chain_collect_stmt(body, source, lsp_line, lsp_char, best);
|
|
945
957
|
}
|
|
@@ -1414,6 +1426,13 @@ fn walk_stmt_resolve(
|
|
|
1414
1426
|
iterable,
|
|
1415
1427
|
body,
|
|
1416
1428
|
..
|
|
1429
|
+
}
|
|
1430
|
+
| Statement::ForIn {
|
|
1431
|
+
name,
|
|
1432
|
+
name_span,
|
|
1433
|
+
object: iterable,
|
|
1434
|
+
body,
|
|
1435
|
+
..
|
|
1417
1436
|
} => {
|
|
1418
1437
|
if *name_span == tgt_span && name.as_ref() == target.name.as_ref() {
|
|
1419
1438
|
return Some(*name_span);
|
|
@@ -2028,6 +2047,13 @@ fn check_unresolved_stmt(
|
|
|
2028
2047
|
iterable,
|
|
2029
2048
|
body,
|
|
2030
2049
|
..
|
|
2050
|
+
}
|
|
2051
|
+
| Statement::ForIn {
|
|
2052
|
+
name,
|
|
2053
|
+
name_span,
|
|
2054
|
+
object: iterable,
|
|
2055
|
+
body,
|
|
2056
|
+
..
|
|
2031
2057
|
} => {
|
|
2032
2058
|
check_unresolved_expr(iterable, scopes, out);
|
|
2033
2059
|
scopes.push();
|
|
@@ -2556,6 +2582,13 @@ fn enumerate_stmt(stmt: &Statement, exported: bool, out: &mut Vec<BindingSite>)
|
|
|
2556
2582
|
iterable,
|
|
2557
2583
|
body,
|
|
2558
2584
|
..
|
|
2585
|
+
}
|
|
2586
|
+
| Statement::ForIn {
|
|
2587
|
+
name,
|
|
2588
|
+
name_span,
|
|
2589
|
+
object: iterable,
|
|
2590
|
+
body,
|
|
2591
|
+
..
|
|
2559
2592
|
} => {
|
|
2560
2593
|
out.push(BindingSite {
|
|
2561
2594
|
name: name.clone(),
|
|
@@ -2731,7 +2764,12 @@ fn jsx_tags_stmt(s: &Statement, out: &mut std::collections::HashSet<Arc<str>>) {
|
|
|
2731
2764
|
}
|
|
2732
2765
|
jsx_tags_stmt(body, out);
|
|
2733
2766
|
}
|
|
2734
|
-
Statement::ForOf { iterable, body, .. }
|
|
2767
|
+
Statement::ForOf { iterable, body, .. }
|
|
2768
|
+
| Statement::ForIn {
|
|
2769
|
+
object: iterable,
|
|
2770
|
+
body,
|
|
2771
|
+
..
|
|
2772
|
+
} => {
|
|
2735
2773
|
jsx_tags_expr(iterable, out);
|
|
2736
2774
|
jsx_tags_stmt(body, out);
|
|
2737
2775
|
}
|
|
@@ -3057,6 +3095,13 @@ fn collect_block_locals(
|
|
|
3057
3095
|
body,
|
|
3058
3096
|
span,
|
|
3059
3097
|
..
|
|
3098
|
+
}
|
|
3099
|
+
| Statement::ForIn {
|
|
3100
|
+
name,
|
|
3101
|
+
name_span,
|
|
3102
|
+
body,
|
|
3103
|
+
span,
|
|
3104
|
+
..
|
|
3060
3105
|
} => {
|
|
3061
3106
|
if pos::span_contains_lsp_position(source, span, lsp_line, lsp_char)
|
|
3062
3107
|
&& pos::span_contains_lsp_position(
|
|
@@ -3428,6 +3473,12 @@ fn walk_stmt_completion(
|
|
|
3428
3473
|
iterable,
|
|
3429
3474
|
body,
|
|
3430
3475
|
..
|
|
3476
|
+
}
|
|
3477
|
+
| Statement::ForIn {
|
|
3478
|
+
name,
|
|
3479
|
+
object: iterable,
|
|
3480
|
+
body,
|
|
3481
|
+
..
|
|
3431
3482
|
} => {
|
|
3432
3483
|
walk_expr_completion(iterable, source, lsp_line, lsp_char, stack, best);
|
|
3433
3484
|
let body_sp = body.as_ref().span();
|
|
@@ -3900,7 +3951,12 @@ fn tref_stmt(stmt: &Statement, out: &mut Vec<TypeOcc>) {
|
|
|
3900
3951
|
}
|
|
3901
3952
|
tref_stmt(body, out);
|
|
3902
3953
|
}
|
|
3903
|
-
Statement::ForOf { iterable, body, .. }
|
|
3954
|
+
Statement::ForOf { iterable, body, .. }
|
|
3955
|
+
| Statement::ForIn {
|
|
3956
|
+
object: iterable,
|
|
3957
|
+
body,
|
|
3958
|
+
..
|
|
3959
|
+
} => {
|
|
3904
3960
|
tref_expr(iterable, out);
|
|
3905
3961
|
tref_stmt(body, out);
|
|
3906
3962
|
}
|
|
@@ -4061,7 +4117,12 @@ fn refs_stmt(
|
|
|
4061
4117
|
}
|
|
4062
4118
|
refs_stmt(body, program, source, name, def_span, out);
|
|
4063
4119
|
}
|
|
4064
|
-
Statement::ForOf { iterable, body, .. }
|
|
4120
|
+
Statement::ForOf { iterable, body, .. }
|
|
4121
|
+
| Statement::ForIn {
|
|
4122
|
+
object: iterable,
|
|
4123
|
+
body,
|
|
4124
|
+
..
|
|
4125
|
+
} => {
|
|
4065
4126
|
refs_expr(iterable, program, source, name, def_span, out);
|
|
4066
4127
|
refs_stmt(body, program, source, name, def_span, out);
|
|
4067
4128
|
}
|
|
@@ -4657,7 +4718,7 @@ mod tests {
|
|
|
4657
4718
|
|
|
4658
4719
|
#[test]
|
|
4659
4720
|
fn mutual_recursion_not_unresolved_or_unused() {
|
|
4660
|
-
let src = "fn even(n) { if (n
|
|
4721
|
+
let src = "fn even(n) { if (n === 0) { return true } return odd(n - 1) }\nfn odd(n) { if (n === 0) { return false } return even(n - 1) }\neven(10)\n";
|
|
4661
4722
|
let program = parse(src).expect("parse");
|
|
4662
4723
|
assert!(
|
|
4663
4724
|
collect_unresolved_identifiers(&program).is_empty(),
|
|
@@ -52,7 +52,9 @@ http-hyper = [
|
|
|
52
52
|
"tokio/rt",
|
|
53
53
|
"tokio/io-util",
|
|
54
54
|
]
|
|
55
|
-
fs
|
|
55
|
+
# `fs/promises` returns settled promises, so fs pulls in the promise machinery.
|
|
56
|
+
# `mkdtemp` needs a random suffix (predictable/timestamp names are a temp-dir security smell).
|
|
57
|
+
fs = ["promise", "dep:rand"]
|
|
56
58
|
process = []
|
|
57
59
|
# Interactive terminal I/O (issue #101): raw mode, key/resize events, size, alt screen.
|
|
58
60
|
tty = ["dep:crossterm"]
|
|
@@ -81,6 +83,8 @@ reqwest = { version = "0.13.2", default-features = false, features = ["rustls",
|
|
|
81
83
|
futures = { version = "0.3.32", optional = true }
|
|
82
84
|
bytes = { version = "1", optional = true }
|
|
83
85
|
tiny_http = { version = "0.12.0", optional = true }
|
|
86
|
+
# fs feature only: random suffix for `mkdtemp`.
|
|
87
|
+
rand = { version = "0.10.1", optional = true }
|
|
84
88
|
socket2 = { version = "0.5", features = ["all"], optional = true }
|
|
85
89
|
libc = { version = "0.2", optional = true }
|
|
86
90
|
arc-swap = { version = "1", optional = true }
|