@tishlang/tish-lsp 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-lsp +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/src/lib.rs +23 -4
- package/crates/tish_lsp/Cargo.toml +1 -1
- 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-lsp +0 -0
- package/platform/darwin-x64/tish-lsp +0 -0
- package/platform/linux-arm64/tish-lsp +0 -0
- package/platform/linux-x64/tish-lsp +0 -0
- package/platform/win32-x64/tish-lsp.exe +0 -0
|
@@ -79,7 +79,12 @@ impl UsageAnalyzer {
|
|
|
79
79
|
}
|
|
80
80
|
self.analyze_statement(body);
|
|
81
81
|
}
|
|
82
|
-
Statement::ForOf { iterable, body, .. }
|
|
82
|
+
Statement::ForOf { iterable, body, .. }
|
|
83
|
+
| Statement::ForIn {
|
|
84
|
+
object: iterable,
|
|
85
|
+
body,
|
|
86
|
+
..
|
|
87
|
+
} => {
|
|
83
88
|
self.analyze_expr(iterable);
|
|
84
89
|
self.analyze_statement(body);
|
|
85
90
|
}
|
|
@@ -330,6 +335,7 @@ fn program_uses_async(program: &Program) -> bool {
|
|
|
330
335
|
Statement::While { body, .. }
|
|
331
336
|
| Statement::For { body, .. }
|
|
332
337
|
| Statement::ForOf { body, .. }
|
|
338
|
+
| Statement::ForIn { body, .. }
|
|
333
339
|
| Statement::DoWhile { body, .. } => stmt_has_async(body),
|
|
334
340
|
Statement::Switch {
|
|
335
341
|
cases,
|
|
@@ -464,7 +470,12 @@ fn program_uses_async(program: &Program) -> bool {
|
|
|
464
470
|
|| update.as_ref().is_some_and(expr_has_await)
|
|
465
471
|
|| stmt_has_await(body)
|
|
466
472
|
}
|
|
467
|
-
Statement::ForOf { iterable, body, .. }
|
|
473
|
+
Statement::ForOf { iterable, body, .. }
|
|
474
|
+
| Statement::ForIn {
|
|
475
|
+
object: iterable,
|
|
476
|
+
body,
|
|
477
|
+
..
|
|
478
|
+
} => {
|
|
468
479
|
expr_has_await(iterable) || stmt_has_await(body)
|
|
469
480
|
}
|
|
470
481
|
Statement::Return { value, .. } => value.as_ref().is_some_and(expr_has_await),
|
|
@@ -917,6 +928,13 @@ pub(crate) struct Codegen {
|
|
|
917
928
|
module_const_f64_aliases: std::collections::HashMap<String, String>,
|
|
918
929
|
/// #181: locals initialized with `new Map()` — direct `map_has`/`get`/`set`/`values` dispatch.
|
|
919
930
|
map_instance_locals: std::collections::HashSet<String>,
|
|
931
|
+
/// #456 regression guard: locals initialized with `new Map()` OR `new Set()`. Arrays have no
|
|
932
|
+
/// runtime method table (`get_prop` returns Null for a method name), so `keys`/`values`/`entries`
|
|
933
|
+
/// MUST use the array fast-path — but that same fast-path would hijack a Map/Set's own
|
|
934
|
+
/// `keys`/`values`/`entries` (which DO resolve as bound methods via `get_prop`). So the array
|
|
935
|
+
/// fast-path fires by default and is suppressed only for a known collection local, which then
|
|
936
|
+
/// falls through to the generic bound-method call.
|
|
937
|
+
collection_instance_locals: std::collections::HashSet<String>,
|
|
920
938
|
/// String-scan hoist: inside a `for (i; i < s.length; …)` loop over an invariant native `String`
|
|
921
939
|
/// `s`, the loop body's `s.charCodeAt/charAt/at(i)` route to O(1) `Vec<char>` slice ops instead
|
|
922
940
|
/// of `str_char_code_at`'s `chars().nth(i)` = O(i). Maps the source ident → the hoisted `_scN`
|
|
@@ -1143,6 +1161,7 @@ impl Codegen {
|
|
|
1143
1161
|
module_const_f64_cum: std::collections::HashMap::new(),
|
|
1144
1162
|
module_const_f64_aliases: std::collections::HashMap::new(),
|
|
1145
1163
|
map_instance_locals: std::collections::HashSet::new(),
|
|
1164
|
+
collection_instance_locals: std::collections::HashSet::new(),
|
|
1146
1165
|
hoisted_string_chars: std::collections::HashMap::new(),
|
|
1147
1166
|
native_fns: std::collections::HashSet::new(),
|
|
1148
1167
|
native_fn_body_emit: false,
|
|
@@ -1308,6 +1327,7 @@ impl Codegen {
|
|
|
1308
1327
|
}
|
|
1309
1328
|
Statement::For { body, .. }
|
|
1310
1329
|
| Statement::ForOf { body, .. }
|
|
1330
|
+
| Statement::ForIn { body, .. }
|
|
1311
1331
|
| Statement::While { body, .. }
|
|
1312
1332
|
| Statement::DoWhile { body, .. } => {
|
|
1313
1333
|
Self::walk_type_aliases(std::slice::from_ref(body.as_ref()), out);
|
|
@@ -1590,13 +1610,59 @@ impl Codegen {
|
|
|
1590
1610
|
fn builtin_native_module_rust_init(&self, spec: &str, export_name: &str) -> Option<String> {
|
|
1591
1611
|
let init = match spec {
|
|
1592
1612
|
"tish:fs" if self.has_feature("fs") => match export_name {
|
|
1593
|
-
|
|
1594
|
-
"
|
|
1613
|
+
// tish names → the dual fs_ext functions (sync, or Node callback if a fn is passed)
|
|
1614
|
+
"readFile" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::read_file(args))"),
|
|
1615
|
+
"writeFile" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::write_file(args))"),
|
|
1595
1616
|
"fileExists" => Some("Value::native(|args: &[Value]| tish_file_exists(args))"),
|
|
1596
1617
|
"isDir" => Some("Value::native(|args: &[Value]| tish_is_dir(args))"),
|
|
1597
|
-
"readDir" => Some("Value::native(|args: &[Value]|
|
|
1598
|
-
"readFileBytes" => Some("Value::native(|args: &[Value]|
|
|
1599
|
-
|
|
1618
|
+
"readDir" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::readdir(args))"),
|
|
1619
|
+
"readFileBytes" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::read_file_bytes(args))"),
|
|
1620
|
+
// Node-compatible names + new methods (sync) — issue #122
|
|
1621
|
+
"readFileSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::read_file(args))"),
|
|
1622
|
+
"readFileBytesSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::read_file_bytes(args))"),
|
|
1623
|
+
"writeFileSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::write_file(args))"),
|
|
1624
|
+
"appendFile" | "appendFileSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::append_file(args))"),
|
|
1625
|
+
"existsSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::exists(args))"),
|
|
1626
|
+
"mkdir" | "mkdirSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::mkdir(args))"),
|
|
1627
|
+
"readdir" | "readdirSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::readdir(args))"),
|
|
1628
|
+
"stat" | "statSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::stat(args))"),
|
|
1629
|
+
"lstat" | "lstatSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::lstat(args))"),
|
|
1630
|
+
"rm" | "rmSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::rm(args))"),
|
|
1631
|
+
"rmdir" | "rmdirSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::rmdir(args))"),
|
|
1632
|
+
"unlink" | "unlinkSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::unlink(args))"),
|
|
1633
|
+
"rename" | "renameSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::rename(args))"),
|
|
1634
|
+
"copyFile" | "copyFileSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::copy_file(args))"),
|
|
1635
|
+
"realpath" | "realpathSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::realpath(args))"),
|
|
1636
|
+
"readlink" | "readlinkSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::readlink(args))"),
|
|
1637
|
+
"truncate" | "truncateSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::truncate(args))"),
|
|
1638
|
+
"mkdtemp" | "mkdtempSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::mkdtemp(args))"),
|
|
1639
|
+
"cp" | "cpSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::cp(args))"),
|
|
1640
|
+
"access" | "accessSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::access(args))"),
|
|
1641
|
+
"constants" => Some("tishlang_runtime::fs_ext::constants()"),
|
|
1642
|
+
_ => None,
|
|
1643
|
+
},
|
|
1644
|
+
"tish:fs/promises" if self.has_feature("fs") => match export_name {
|
|
1645
|
+
"readFile" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::read_file_promise(args))"),
|
|
1646
|
+
"readFileBytes" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::read_file_bytes_promise(args))"),
|
|
1647
|
+
"writeFile" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::write_file_promise(args))"),
|
|
1648
|
+
"appendFile" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::append_file_promise(args))"),
|
|
1649
|
+
"mkdir" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::mkdir_promise(args))"),
|
|
1650
|
+
"readdir" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::readdir_promise(args))"),
|
|
1651
|
+
"stat" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::stat_promise(args))"),
|
|
1652
|
+
"lstat" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::lstat_promise(args))"),
|
|
1653
|
+
"rm" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::rm_promise(args))"),
|
|
1654
|
+
"rmdir" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::rmdir_promise(args))"),
|
|
1655
|
+
"unlink" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::unlink_promise(args))"),
|
|
1656
|
+
"rename" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::rename_promise(args))"),
|
|
1657
|
+
"copyFile" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::copy_file_promise(args))"),
|
|
1658
|
+
"realpath" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::realpath_promise(args))"),
|
|
1659
|
+
"readlink" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::readlink_promise(args))"),
|
|
1660
|
+
"truncate" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::truncate_promise(args))"),
|
|
1661
|
+
"mkdtemp" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::mkdtemp_promise(args))"),
|
|
1662
|
+
"cp" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::cp_promise(args))"),
|
|
1663
|
+
"access" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::access_promise(args))"),
|
|
1664
|
+
"exists" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::exists_promise(args))"),
|
|
1665
|
+
"constants" => Some("tishlang_runtime::fs_ext::constants()"),
|
|
1600
1666
|
_ => None,
|
|
1601
1667
|
},
|
|
1602
1668
|
"tish:http" if self.has_feature("http") => match export_name {
|
|
@@ -2175,7 +2241,7 @@ impl Codegen {
|
|
|
2175
2241
|
self.write("use std::cell::RefCell;\n");
|
|
2176
2242
|
self.write("use std::rc::Rc;\n");
|
|
2177
2243
|
self.write("use std::sync::Arc;\n");
|
|
2178
|
-
self.write("use tishlang_runtime::{console_debug as tish_console_debug, console_info as tish_console_info, console_log as tish_console_log, console_warn as tish_console_warn, console_error as tish_console_error, boolean as tish_boolean, decode_uri as tish_decode_uri, encode_uri as tish_encode_uri, string_escape_html_impl as tish_escape_html, in_operator as tish_in_operator, is_finite as tish_is_finite, is_nan as tish_is_nan, json_parse as tish_json_parse, json_stringify as tish_json_stringify, math_abs as tish_math_abs, math_ceil as tish_math_ceil, math_floor as tish_math_floor, math_max as tish_math_max, math_min as tish_math_min, math_round as tish_math_round, math_sqrt as tish_math_sqrt, parse_float as tish_parse_float, parse_int as tish_parse_int, math_random as tish_math_random, math_pow as tish_math_pow, math_sin as tish_math_sin, math_cos as tish_math_cos, math_tan as tish_math_tan, math_log as tish_math_log, math_exp as tish_math_exp, math_sign as tish_math_sign, math_trunc as tish_math_trunc, math_imul as tish_math_imul, math_sinh as tish_math_sinh, math_cosh as tish_math_cosh, math_tanh as tish_math_tanh, math_asinh as tish_math_asinh, math_acosh as tish_math_acosh, math_atanh as tish_math_atanh, math_cbrt as tish_math_cbrt, math_log2 as tish_math_log2, math_log10 as tish_math_log10, math_hypot as tish_math_hypot, math_atan2 as tish_math_atan2, math_asin as tish_math_asin, math_acos as tish_math_acos, math_atan as tish_math_atan, array_is_array as tish_array_is_array, array_construct as tish_array_construct, string_from_char_code as tish_string_from_char_code, string_convert as tish_string_convert, number_convert as tish_number_convert, object_assign as tish_object_assign, object_keys as tish_object_keys, object_values as tish_object_values, object_entries as tish_object_entries, object_from_entries as tish_object_from_entries, symbol_object as tish_symbol_object, tish_construct, tish_error_constructor, tish_date_constructor, tish_set_constructor, tish_map_constructor, map_get as tish_map_get, map_has as tish_map_has, map_set as tish_map_set, map_values as tish_map_values, tish_float64_array_constructor, tish_float32_array_constructor, tish_int8_array_constructor, tish_uint8_array_constructor, tish_uint8_clamped_array_constructor, tish_int16_array_constructor, tish_uint16_array_constructor, tish_int32_array_constructor, tish_uint32_array_constructor, tish_audio_context_constructor, ObjectMap, ObjectData, PropMap, TishError, Value, VmRef};\n");
|
|
2244
|
+
self.write("use tishlang_runtime::{console_debug as tish_console_debug, console_info as tish_console_info, console_log as tish_console_log, console_warn as tish_console_warn, console_error as tish_console_error, boolean as tish_boolean, decode_uri as tish_decode_uri, encode_uri as tish_encode_uri, encode_uri_component as tish_encode_uri_component, decode_uri_component as tish_decode_uri_component, string_escape_html_impl as tish_escape_html, in_operator as tish_in_operator, is_finite as tish_is_finite, is_nan as tish_is_nan, json_parse as tish_json_parse, json_stringify as tish_json_stringify, math_abs as tish_math_abs, math_ceil as tish_math_ceil, math_floor as tish_math_floor, math_max as tish_math_max, math_min as tish_math_min, math_round as tish_math_round, math_sqrt as tish_math_sqrt, parse_float as tish_parse_float, parse_int as tish_parse_int, math_random as tish_math_random, math_pow as tish_math_pow, math_sin as tish_math_sin, math_cos as tish_math_cos, math_tan as tish_math_tan, math_log as tish_math_log, math_exp as tish_math_exp, math_sign as tish_math_sign, math_trunc as tish_math_trunc, math_imul as tish_math_imul, math_sinh as tish_math_sinh, math_cosh as tish_math_cosh, math_tanh as tish_math_tanh, math_asinh as tish_math_asinh, math_acosh as tish_math_acosh, math_atanh as tish_math_atanh, math_cbrt as tish_math_cbrt, math_log2 as tish_math_log2, math_log10 as tish_math_log10, math_expm1 as tish_math_expm1, math_log1p as tish_math_log1p, math_clz32 as tish_math_clz32, math_fround as tish_math_fround, math_hypot as tish_math_hypot, math_atan2 as tish_math_atan2, math_asin as tish_math_asin, math_acos as tish_math_acos, math_atan as tish_math_atan, array_is_array as tish_array_is_array, array_of as tish_array_of, array_from as tish_array_from, object_is as tish_object_is, object_has_own as tish_object_has_own, structured_clone as tish_structured_clone, array_construct as tish_array_construct, string_from_char_code as tish_string_from_char_code, string_convert as tish_string_convert, number_convert as tish_number_convert, number_is_integer as tish_number_is_integer, number_is_safe_integer as tish_number_is_safe_integer, number_is_nan as tish_number_is_nan, number_is_finite as tish_number_is_finite, object_assign as tish_object_assign, object_keys as tish_object_keys, object_values as tish_object_values, object_entries as tish_object_entries, object_from_entries as tish_object_from_entries, symbol_object as tish_symbol_object, tish_construct, tish_error_constructor, tish_date_constructor, tish_set_constructor, tish_map_constructor, map_get as tish_map_get, map_has as tish_map_has, map_set as tish_map_set, map_values as tish_map_values, tish_float64_array_constructor, tish_float32_array_constructor, tish_int8_array_constructor, tish_uint8_array_constructor, tish_uint8_clamped_array_constructor, tish_int16_array_constructor, tish_uint16_array_constructor, tish_int32_array_constructor, tish_uint32_array_constructor, tish_audio_context_constructor, ObjectMap, ObjectData, PropMap, TishError, Value, VmRef};\n");
|
|
2179
2245
|
if self.program_has_jsx {
|
|
2180
2246
|
self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
|
|
2181
2247
|
}
|
|
@@ -2195,6 +2261,11 @@ impl Codegen {
|
|
|
2195
2261
|
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve};\n");
|
|
2196
2262
|
}
|
|
2197
2263
|
}
|
|
2264
|
+
// `fs/promises` (and any promise-using async program) needs the await machinery even
|
|
2265
|
+
// without http. `fs` implies the runtime `promise` feature, so these resolve.
|
|
2266
|
+
if self.is_async && !self.has_feature("http") && self.has_feature("fs") {
|
|
2267
|
+
self.write("use tishlang_runtime::{promise_object as tish_promise_object, await_promise as tish_await_promise, await_promise_throw as tish_await_promise_throw};\n");
|
|
2268
|
+
}
|
|
2198
2269
|
if self.has_feature("fs") {
|
|
2199
2270
|
self.write("use tishlang_runtime::{read_file as tish_read_file, read_file_bytes as tish_read_file_bytes, write_file as tish_write_file, file_exists as tish_file_exists, is_dir as tish_is_dir, read_dir as tish_read_dir, mkdir as tish_mkdir};\n");
|
|
2200
2271
|
}
|
|
@@ -2378,6 +2449,9 @@ impl Codegen {
|
|
|
2378
2449
|
self.writeln("let parseFloat = Value::native(|args: &[Value]| tish_parse_float(args));");
|
|
2379
2450
|
self.writeln("let decodeURI = Value::native(|args: &[Value]| tish_decode_uri(args));");
|
|
2380
2451
|
self.writeln("let encodeURI = Value::native(|args: &[Value]| tish_encode_uri(args));");
|
|
2452
|
+
self.writeln("let encodeURIComponent = Value::native(|args: &[Value]| tish_encode_uri_component(args));");
|
|
2453
|
+
self.writeln("let decodeURIComponent = Value::native(|args: &[Value]| tish_decode_uri_component(args));");
|
|
2454
|
+
self.writeln("let structuredClone = Value::native(|args: &[Value]| tish_structured_clone(args));");
|
|
2381
2455
|
// `registerStaticRoute` calls the http-gated runtime fn, so only bind it when http is linked
|
|
2382
2456
|
// (matches the conditional `use` above; otherwise non-http builds fail to resolve it).
|
|
2383
2457
|
if self.has_feature("http") {
|
|
@@ -2439,6 +2513,10 @@ impl Codegen {
|
|
|
2439
2513
|
("cbrt", "tish_math_cbrt"),
|
|
2440
2514
|
("log2", "tish_math_log2"),
|
|
2441
2515
|
("log10", "tish_math_log10"),
|
|
2516
|
+
("expm1", "tish_math_expm1"),
|
|
2517
|
+
("log1p", "tish_math_log1p"),
|
|
2518
|
+
("clz32", "tish_math_clz32"),
|
|
2519
|
+
("fround", "tish_math_fround"),
|
|
2442
2520
|
("hypot", "tish_math_hypot"),
|
|
2443
2521
|
("atan2", "tish_math_atan2"),
|
|
2444
2522
|
("asin", "tish_math_asin"),
|
|
@@ -2467,6 +2545,12 @@ impl Codegen {
|
|
|
2467
2545
|
self.writeln(
|
|
2468
2546
|
"(Arc::from(\"isArray\"), Value::native(|args: &[Value]| tish_array_is_array(args))),",
|
|
2469
2547
|
);
|
|
2548
|
+
self.writeln(
|
|
2549
|
+
"(Arc::from(\"of\"), Value::native(|args: &[Value]| tish_array_of(args))),",
|
|
2550
|
+
);
|
|
2551
|
+
self.writeln(
|
|
2552
|
+
"(Arc::from(\"from\"), Value::native(|args: &[Value]| tish_array_from(args))),",
|
|
2553
|
+
);
|
|
2470
2554
|
// `Array(n)` / `new Array(n)` constructor (issue #72); `__call` covers both forms.
|
|
2471
2555
|
self.writeln(
|
|
2472
2556
|
"(Arc::from(\"__call\"), Value::native(|args: &[Value]| tish_array_construct(args))),",
|
|
@@ -2482,10 +2566,24 @@ impl Codegen {
|
|
|
2482
2566
|
self.indent -= 1;
|
|
2483
2567
|
self.writeln("]));");
|
|
2484
2568
|
|
|
2485
|
-
// `Number(value)` coercion callable (issue #36).
|
|
2569
|
+
// `Number(value)` coercion callable (issue #36) + the `Number.*` statics.
|
|
2486
2570
|
self.writeln("let Number = Value::object(ObjectMap::from([");
|
|
2487
2571
|
self.indent += 1;
|
|
2488
2572
|
self.writeln("(Arc::from(\"__call\"), Value::native(|args: &[Value]| tish_number_convert(args))),");
|
|
2573
|
+
self.writeln("(Arc::from(\"isInteger\"), Value::native(|args: &[Value]| tish_number_is_integer(args))),");
|
|
2574
|
+
self.writeln("(Arc::from(\"isSafeInteger\"), Value::native(|args: &[Value]| tish_number_is_safe_integer(args))),");
|
|
2575
|
+
self.writeln("(Arc::from(\"isNaN\"), Value::native(|args: &[Value]| tish_number_is_nan(args))),");
|
|
2576
|
+
self.writeln("(Arc::from(\"isFinite\"), Value::native(|args: &[Value]| tish_number_is_finite(args))),");
|
|
2577
|
+
self.writeln("(Arc::from(\"parseInt\"), Value::native(|args: &[Value]| tish_parse_int(args))),");
|
|
2578
|
+
self.writeln("(Arc::from(\"parseFloat\"), Value::native(|args: &[Value]| tish_parse_float(args))),");
|
|
2579
|
+
self.writeln("(Arc::from(\"MAX_SAFE_INTEGER\"), Value::Number(9007199254740991.0)),");
|
|
2580
|
+
self.writeln("(Arc::from(\"MIN_SAFE_INTEGER\"), Value::Number(-9007199254740991.0)),");
|
|
2581
|
+
self.writeln("(Arc::from(\"EPSILON\"), Value::Number(f64::EPSILON)),");
|
|
2582
|
+
self.writeln("(Arc::from(\"MAX_VALUE\"), Value::Number(f64::MAX)),");
|
|
2583
|
+
self.writeln("(Arc::from(\"MIN_VALUE\"), Value::Number(5e-324)),");
|
|
2584
|
+
self.writeln("(Arc::from(\"POSITIVE_INFINITY\"), Value::Number(f64::INFINITY)),");
|
|
2585
|
+
self.writeln("(Arc::from(\"NEGATIVE_INFINITY\"), Value::Number(f64::NEG_INFINITY)),");
|
|
2586
|
+
self.writeln("(Arc::from(\"NaN\"), Value::Number(f64::NAN)),");
|
|
2489
2587
|
self.indent -= 1;
|
|
2490
2588
|
self.writeln("]));");
|
|
2491
2589
|
|
|
@@ -2500,6 +2598,15 @@ impl Codegen {
|
|
|
2500
2598
|
self.writeln(
|
|
2501
2599
|
"(Arc::from(\"assign\"), Value::native(|args: &[Value]| tish_object_assign(args))),",
|
|
2502
2600
|
);
|
|
2601
|
+
self.writeln(
|
|
2602
|
+
"(Arc::from(\"is\"), Value::native(|args: &[Value]| tish_object_is(args))),",
|
|
2603
|
+
);
|
|
2604
|
+
self.writeln(
|
|
2605
|
+
"(Arc::from(\"hasOwn\"), Value::native(|args: &[Value]| tish_object_has_own(args))),",
|
|
2606
|
+
);
|
|
2607
|
+
self.writeln(
|
|
2608
|
+
"(Arc::from(\"getOwnPropertyNames\"), Value::native(|args: &[Value]| tish_object_keys(args))),",
|
|
2609
|
+
);
|
|
2503
2610
|
self.writeln(
|
|
2504
2611
|
"(Arc::from(\"keys\"), Value::native(|args: &[Value]| tish_object_keys(args))),",
|
|
2505
2612
|
);
|
|
@@ -4590,6 +4697,11 @@ impl Codegen {
|
|
|
4590
4697
|
if Self::expr_is_map_construct(init_e) {
|
|
4591
4698
|
self.map_instance_locals.insert(name.to_string());
|
|
4592
4699
|
}
|
|
4700
|
+
// #456 regression: a `new Map()`/`new Set()` local must not have its
|
|
4701
|
+
// keys/values/entries hijacked by the array fast-path.
|
|
4702
|
+
if Self::expr_is_map_construct(init_e) || Self::expr_is_set_construct(init_e) {
|
|
4703
|
+
self.collection_instance_locals.insert(name.to_string());
|
|
4704
|
+
}
|
|
4593
4705
|
}
|
|
4594
4706
|
|
|
4595
4707
|
// `let cum = cumulative_nv(&probs)` inherits `probs`' fixed length for in-bounds reads.
|
|
@@ -4805,6 +4917,46 @@ impl Codegen {
|
|
|
4805
4917
|
self.indent -= 1;
|
|
4806
4918
|
self.writeln("}");
|
|
4807
4919
|
}
|
|
4920
|
+
Statement::ForIn {
|
|
4921
|
+
name,
|
|
4922
|
+
name_span,
|
|
4923
|
+
object,
|
|
4924
|
+
body,
|
|
4925
|
+
span,
|
|
4926
|
+
} => {
|
|
4927
|
+
// Lower `for (let k in obj)` to `for (let k of Object.keys(obj))` (#413) and reuse the
|
|
4928
|
+
// native for-of emission. Object.keys yields the own enumerable keys the interpreter
|
|
4929
|
+
// enumerates (insertion order for objects, index strings for arrays, `[]` for a
|
|
4930
|
+
// non-object → `for (k in null)` no-ops).
|
|
4931
|
+
let dummy = Span {
|
|
4932
|
+
start: (0, 0),
|
|
4933
|
+
end: (0, 0),
|
|
4934
|
+
};
|
|
4935
|
+
let keys_call = Expr::Call {
|
|
4936
|
+
callee: Box::new(Expr::Member {
|
|
4937
|
+
object: Box::new(Expr::Ident {
|
|
4938
|
+
name: Arc::from("Object"),
|
|
4939
|
+
span: dummy,
|
|
4940
|
+
}),
|
|
4941
|
+
prop: MemberProp::Name {
|
|
4942
|
+
name: Arc::from("keys"),
|
|
4943
|
+
span: dummy,
|
|
4944
|
+
},
|
|
4945
|
+
optional: false,
|
|
4946
|
+
span: dummy,
|
|
4947
|
+
}),
|
|
4948
|
+
args: vec![CallArg::Expr(object.clone())],
|
|
4949
|
+
span: dummy,
|
|
4950
|
+
};
|
|
4951
|
+
let lowered = Statement::ForOf {
|
|
4952
|
+
name: Arc::clone(name),
|
|
4953
|
+
name_span: *name_span,
|
|
4954
|
+
iterable: keys_call,
|
|
4955
|
+
body: body.clone(),
|
|
4956
|
+
span: *span,
|
|
4957
|
+
};
|
|
4958
|
+
self.emit_statement(&lowered)?;
|
|
4959
|
+
}
|
|
4808
4960
|
Statement::ForOf {
|
|
4809
4961
|
name,
|
|
4810
4962
|
iterable,
|
|
@@ -5591,6 +5743,9 @@ impl Codegen {
|
|
|
5591
5743
|
"isFinite",
|
|
5592
5744
|
"encodeURI",
|
|
5593
5745
|
"decodeURI",
|
|
5746
|
+
"encodeURIComponent",
|
|
5747
|
+
"decodeURIComponent",
|
|
5748
|
+
"structuredClone",
|
|
5594
5749
|
"htmlEscape",
|
|
5595
5750
|
"registerStaticRoute",
|
|
5596
5751
|
"String",
|
|
@@ -6728,9 +6883,15 @@ impl Codegen {
|
|
|
6728
6883
|
return Ok(format!("tish_map_get({}, {})", map_ref, key_ref));
|
|
6729
6884
|
}
|
|
6730
6885
|
"set" if args.len() == 2 => {
|
|
6886
|
+
// Borrow the key/val places (like `get`/`has`) so a keyed local
|
|
6887
|
+
// isn't MOVED into the call and can still be used afterward — the
|
|
6888
|
+
// runtime `map_set` clones on insert (#442). `&Value` args also
|
|
6889
|
+
// route native-typed operands through the boxed path correctly.
|
|
6890
|
+
let key_ref = self.emit_borrowed_arg(&args[0])?;
|
|
6891
|
+
let val_ref = self.emit_borrowed_arg(&args[1])?;
|
|
6731
6892
|
return Ok(format!(
|
|
6732
6893
|
"tish_map_set({}, {}, {})",
|
|
6733
|
-
map_ref,
|
|
6894
|
+
map_ref, key_ref, val_ref
|
|
6734
6895
|
));
|
|
6735
6896
|
}
|
|
6736
6897
|
"values" if args.is_empty() => {
|
|
@@ -6786,9 +6947,14 @@ impl Codegen {
|
|
|
6786
6947
|
"indexOf" => {
|
|
6787
6948
|
let search = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6788
6949
|
let from = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6950
|
+
// Array `indexOf` takes `Option<&Value>` fromIndex; string keeps the &Value/Null form.
|
|
6951
|
+
let from_opt = match arg_exprs.get(1) {
|
|
6952
|
+
Some(e) => format!("Some(&{})", e),
|
|
6953
|
+
None => "None".to_string(),
|
|
6954
|
+
};
|
|
6789
6955
|
return Ok(format!(
|
|
6790
|
-
"{{ let _obj = ({}).clone(); match &_obj {{ Value::Array(_) => tishlang_runtime::array_index_of(&_obj, &{}), Value::String(_) => tishlang_runtime::string_index_of(&_obj, &{}, &{}), _ => Value::Number(-1.0) }} }}",
|
|
6791
|
-
obj_expr, search, search, from
|
|
6956
|
+
"{{ let _obj = ({}).clone(); match &_obj {{ Value::Array(_) => tishlang_runtime::array_index_of(&_obj, &{}, {}), Value::String(_) => tishlang_runtime::string_index_of(&_obj, &{}, &{}), _ => Value::Number(-1.0) }} }}",
|
|
6957
|
+
obj_expr, search, from_opt, search, from
|
|
6792
6958
|
));
|
|
6793
6959
|
}
|
|
6794
6960
|
"lastIndexOf" => {
|
|
@@ -6798,9 +6964,23 @@ impl Codegen {
|
|
|
6798
6964
|
} else {
|
|
6799
6965
|
"Value::Number(f64::INFINITY)".to_string()
|
|
6800
6966
|
};
|
|
6967
|
+
// Array lastIndexOf takes Option<&Value> fromIndex; string keeps &Value.
|
|
6968
|
+
let from_opt = match arg_exprs.get(1) {
|
|
6969
|
+
Some(e) => format!("Some(&{})", e),
|
|
6970
|
+
None => "None".to_string(),
|
|
6971
|
+
};
|
|
6972
|
+
return Ok(format!(
|
|
6973
|
+
"{{ let _obj = ({}).clone(); match &_obj {{ Value::Array(_) => tishlang_runtime::array_last_index_of(&_obj, &{}, {}), Value::String(_) => tishlang_runtime::string_last_index_of(&_obj, &{}, &{}), _ => Value::Number(-1.0) }} }}",
|
|
6974
|
+
obj_expr, search, from_opt, search, position
|
|
6975
|
+
));
|
|
6976
|
+
}
|
|
6977
|
+
"copyWithin" => {
|
|
6978
|
+
let t = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6979
|
+
let s = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6980
|
+
let e = arg_exprs.get(2).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6801
6981
|
return Ok(format!(
|
|
6802
|
-
"
|
|
6803
|
-
obj_expr,
|
|
6982
|
+
"tishlang_runtime::array_copy_within(&{}, &{}, &{}, &{})",
|
|
6983
|
+
obj_expr, t, s, e
|
|
6804
6984
|
));
|
|
6805
6985
|
}
|
|
6806
6986
|
"includes" => {
|
|
@@ -6821,6 +7001,9 @@ impl Codegen {
|
|
|
6821
7001
|
"reverse" => {
|
|
6822
7002
|
return Ok(format!("tishlang_runtime::array_reverse(&{})", obj_expr));
|
|
6823
7003
|
}
|
|
7004
|
+
"toReversed" => {
|
|
7005
|
+
return Ok(format!("tishlang_runtime::array_to_reversed(&{})", obj_expr));
|
|
7006
|
+
}
|
|
6824
7007
|
"fill" => {
|
|
6825
7008
|
let value = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6826
7009
|
let start = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
@@ -6879,6 +7062,26 @@ impl Codegen {
|
|
|
6879
7062
|
"trim" => {
|
|
6880
7063
|
return Ok(format!("tishlang_runtime::string_trim(&{})", obj_expr));
|
|
6881
7064
|
}
|
|
7065
|
+
"trimStart" => {
|
|
7066
|
+
return Ok(format!("tishlang_runtime::string_trim_start(&{})", obj_expr));
|
|
7067
|
+
}
|
|
7068
|
+
"trimEnd" => {
|
|
7069
|
+
return Ok(format!("tishlang_runtime::string_trim_end(&{})", obj_expr));
|
|
7070
|
+
}
|
|
7071
|
+
"normalize" => {
|
|
7072
|
+
let form = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7073
|
+
return Ok(format!(
|
|
7074
|
+
"tishlang_runtime::string_normalize(&{}, &{})",
|
|
7075
|
+
obj_expr, form
|
|
7076
|
+
));
|
|
7077
|
+
}
|
|
7078
|
+
"codePointAt" => {
|
|
7079
|
+
let idx = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7080
|
+
return Ok(format!(
|
|
7081
|
+
"tishlang_runtime::string_code_point_at(&{}, &{})",
|
|
7082
|
+
obj_expr, idx
|
|
7083
|
+
));
|
|
7084
|
+
}
|
|
6882
7085
|
"toUpperCase" => {
|
|
6883
7086
|
return Ok(format!("tishlang_runtime::string_to_upper_case(&{})", obj_expr));
|
|
6884
7087
|
}
|
|
@@ -6887,16 +7090,24 @@ impl Codegen {
|
|
|
6887
7090
|
}
|
|
6888
7091
|
"startsWith" => {
|
|
6889
7092
|
let search = arg_exprs.first().cloned().unwrap_or_else(|| "Value::String(\"\".into())".to_string());
|
|
7093
|
+
let pos = match arg_exprs.get(1) {
|
|
7094
|
+
Some(e) => format!("Some(&{})", e),
|
|
7095
|
+
None => "None".to_string(),
|
|
7096
|
+
};
|
|
6890
7097
|
return Ok(format!(
|
|
6891
|
-
"tishlang_runtime::string_starts_with(&{}, &{})",
|
|
6892
|
-
obj_expr, search
|
|
7098
|
+
"tishlang_runtime::string_starts_with(&{}, &{}, {})",
|
|
7099
|
+
obj_expr, search, pos
|
|
6893
7100
|
));
|
|
6894
7101
|
}
|
|
6895
7102
|
"endsWith" => {
|
|
6896
7103
|
let search = arg_exprs.first().cloned().unwrap_or_else(|| "Value::String(\"\".into())".to_string());
|
|
7104
|
+
let pos = match arg_exprs.get(1) {
|
|
7105
|
+
Some(e) => format!("Some(&{})", e),
|
|
7106
|
+
None => "None".to_string(),
|
|
7107
|
+
};
|
|
6897
7108
|
return Ok(format!(
|
|
6898
|
-
"tishlang_runtime::string_ends_with(&{}, &{})",
|
|
6899
|
-
obj_expr, search
|
|
7109
|
+
"tishlang_runtime::string_ends_with(&{}, &{}, {})",
|
|
7110
|
+
obj_expr, search, pos
|
|
6900
7111
|
));
|
|
6901
7112
|
}
|
|
6902
7113
|
"replace" => {
|
|
@@ -6926,6 +7137,13 @@ impl Codegen {
|
|
|
6926
7137
|
obj_expr, regexp
|
|
6927
7138
|
));
|
|
6928
7139
|
}
|
|
7140
|
+
"matchAll" if self.has_feature("regex") => {
|
|
7141
|
+
let regexp = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7142
|
+
return Ok(format!(
|
|
7143
|
+
"tishlang_runtime::string_match_all_regex(&{}, &{})",
|
|
7144
|
+
obj_expr, regexp
|
|
7145
|
+
));
|
|
7146
|
+
}
|
|
6929
7147
|
"search" if self.has_feature("regex") => {
|
|
6930
7148
|
let regexp = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6931
7149
|
return Ok(format!(
|
|
@@ -6987,6 +7205,20 @@ impl Codegen {
|
|
|
6987
7205
|
obj_expr, digits
|
|
6988
7206
|
));
|
|
6989
7207
|
}
|
|
7208
|
+
"toExponential" => {
|
|
7209
|
+
let digits = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7210
|
+
return Ok(format!(
|
|
7211
|
+
"tishlang_runtime::number_to_exponential(&{}, &{})",
|
|
7212
|
+
obj_expr, digits
|
|
7213
|
+
));
|
|
7214
|
+
}
|
|
7215
|
+
"toPrecision" => {
|
|
7216
|
+
let precision = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7217
|
+
return Ok(format!(
|
|
7218
|
+
"tishlang_runtime::number_to_precision(&{}, &{})",
|
|
7219
|
+
obj_expr, precision
|
|
7220
|
+
));
|
|
7221
|
+
}
|
|
6990
7222
|
"toString" => {
|
|
6991
7223
|
let radix = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6992
7224
|
return Ok(format!(
|
|
@@ -7024,11 +7256,41 @@ impl Codegen {
|
|
|
7024
7256
|
}
|
|
7025
7257
|
}
|
|
7026
7258
|
let callback = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7259
|
+
// `array_reduce` takes `Option<&Value>` init: `None` = no initial value, so an
|
|
7260
|
+
// empty array with no init throws (per JS) instead of returning `null`.
|
|
7261
|
+
let init_arg = match arg_exprs.get(1) {
|
|
7262
|
+
Some(e) => format!("Some(&{})", e),
|
|
7263
|
+
None => "None".to_string(),
|
|
7264
|
+
};
|
|
7265
|
+
return Ok(format!(
|
|
7266
|
+
"tishlang_runtime::array_reduce(&{}, &{}, {})",
|
|
7267
|
+
obj_expr, callback, init_arg
|
|
7268
|
+
));
|
|
7269
|
+
}
|
|
7270
|
+
"reduceRight" => {
|
|
7271
|
+
let callback = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7272
|
+
let init_arg = match arg_exprs.get(1) {
|
|
7273
|
+
Some(e) => format!("Some(&{})", e),
|
|
7274
|
+
None => "None".to_string(),
|
|
7275
|
+
};
|
|
7027
7276
|
return Ok(format!(
|
|
7028
|
-
"tishlang_runtime::
|
|
7029
|
-
obj_expr, callback,
|
|
7277
|
+
"tishlang_runtime::array_reduce_right(&{}, &{}, {})",
|
|
7278
|
+
obj_expr, callback, init_arg
|
|
7030
7279
|
));
|
|
7031
7280
|
}
|
|
7281
|
+
// keys/values/entries exist on BOTH arrays and Map/Set. Arrays have no runtime
|
|
7282
|
+
// method table, so they need this fast-path; a known Map/Set local instead
|
|
7283
|
+
// falls through to the generic bound-method call (its get_prop resolves them).
|
|
7284
|
+
// (#456 regression — the unguarded arms hijacked `m.keys()`/`s.values()`.)
|
|
7285
|
+
"keys" if !self.kve_defers_to_generic(object.as_ref()) => {
|
|
7286
|
+
return Ok(format!("tishlang_runtime::array_keys(&{})", obj_expr));
|
|
7287
|
+
}
|
|
7288
|
+
"values" if !self.kve_defers_to_generic(object.as_ref()) => {
|
|
7289
|
+
return Ok(format!("tishlang_runtime::array_values(&{})", obj_expr));
|
|
7290
|
+
}
|
|
7291
|
+
"entries" if !self.kve_defers_to_generic(object.as_ref()) => {
|
|
7292
|
+
return Ok(format!("tishlang_runtime::array_entries(&{})", obj_expr));
|
|
7293
|
+
}
|
|
7032
7294
|
"forEach" => {
|
|
7033
7295
|
let callback = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7034
7296
|
return Ok(format!(
|
|
@@ -7133,6 +7395,38 @@ impl Codegen {
|
|
|
7133
7395
|
obj_expr, start, delete_count, items
|
|
7134
7396
|
));
|
|
7135
7397
|
}
|
|
7398
|
+
"toSpliced" => {
|
|
7399
|
+
let start = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Number(0.0)".to_string());
|
|
7400
|
+
let delete_count = arg_exprs.get(1).map(|d| format!("Some(&{})", d)).unwrap_or_else(|| "None".to_string());
|
|
7401
|
+
let items = if arg_exprs.len() > 2 {
|
|
7402
|
+
let items_vec = arg_exprs[2..].iter()
|
|
7403
|
+
.map(|a| format!("{}.clone()", a))
|
|
7404
|
+
.collect::<Vec<_>>()
|
|
7405
|
+
.join(", ");
|
|
7406
|
+
format!("&[{}]", items_vec)
|
|
7407
|
+
} else {
|
|
7408
|
+
"&[]".to_string()
|
|
7409
|
+
};
|
|
7410
|
+
return Ok(format!(
|
|
7411
|
+
"tishlang_runtime::array_to_spliced(&{}, &{}, {}, {})",
|
|
7412
|
+
obj_expr, start, delete_count, items
|
|
7413
|
+
));
|
|
7414
|
+
}
|
|
7415
|
+
"toSorted" => {
|
|
7416
|
+
let comparator = arg_exprs.first().map(|c| format!("Some(&{})", c)).unwrap_or_else(|| "None".to_string());
|
|
7417
|
+
return Ok(format!(
|
|
7418
|
+
"tishlang_runtime::array_to_sorted(&{}, {})",
|
|
7419
|
+
obj_expr, comparator
|
|
7420
|
+
));
|
|
7421
|
+
}
|
|
7422
|
+
"with" => {
|
|
7423
|
+
let index = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7424
|
+
let value = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7425
|
+
return Ok(format!(
|
|
7426
|
+
"tishlang_runtime::array_with(&{}, &{}, &{})",
|
|
7427
|
+
obj_expr, index, value
|
|
7428
|
+
));
|
|
7429
|
+
}
|
|
7136
7430
|
"flat" => {
|
|
7137
7431
|
let depth = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Number(1.0)".to_string());
|
|
7138
7432
|
return Ok(format!(
|
|
@@ -8175,7 +8469,12 @@ impl Codegen {
|
|
|
8175
8469
|
}
|
|
8176
8470
|
Self::collect_assigned_idents_in_stmt(body, names);
|
|
8177
8471
|
}
|
|
8178
|
-
Statement::ForOf { iterable, body, .. }
|
|
8472
|
+
Statement::ForOf { iterable, body, .. }
|
|
8473
|
+
| Statement::ForIn {
|
|
8474
|
+
object: iterable,
|
|
8475
|
+
body,
|
|
8476
|
+
..
|
|
8477
|
+
} => {
|
|
8179
8478
|
Self::collect_assigned_idents_in_expr(iterable, names);
|
|
8180
8479
|
Self::collect_assigned_idents_in_stmt(body, names);
|
|
8181
8480
|
}
|
|
@@ -8311,7 +8610,12 @@ impl Codegen {
|
|
|
8311
8610
|
|| update.as_ref().is_some_and(|u| Self::expr_has_length_changer(u))
|
|
8312
8611
|
|| Self::stmt_has_length_changer(body)
|
|
8313
8612
|
}
|
|
8314
|
-
Statement::ForOf { iterable, body, .. }
|
|
8613
|
+
Statement::ForOf { iterable, body, .. }
|
|
8614
|
+
| Statement::ForIn {
|
|
8615
|
+
object: iterable,
|
|
8616
|
+
body,
|
|
8617
|
+
..
|
|
8618
|
+
} => {
|
|
8315
8619
|
Self::expr_has_length_changer(iterable) || Self::stmt_has_length_changer(body)
|
|
8316
8620
|
}
|
|
8317
8621
|
Statement::While { cond, body, .. } | Statement::DoWhile { body, cond, .. } => {
|
|
@@ -8338,6 +8642,7 @@ impl Codegen {
|
|
|
8338
8642
|
| "normalize" | "localeCompare" | "map" | "filter" | "reduce" | "reduceRight"
|
|
8339
8643
|
| "forEach" | "find" | "findIndex" | "findLast" | "findLastIndex" | "some" | "every"
|
|
8340
8644
|
| "join" | "flat" | "flatMap" | "keys" | "values" | "entries"
|
|
8645
|
+
| "toReversed" | "toSorted" | "with" | "toSpliced"
|
|
8341
8646
|
)
|
|
8342
8647
|
}
|
|
8343
8648
|
|
|
@@ -8448,7 +8753,12 @@ impl Codegen {
|
|
|
8448
8753
|
|| update.as_ref().is_some_and(|u| Self::expr_changes_base_len(u, base))
|
|
8449
8754
|
|| Self::stmt_changes_base_len(body, base)
|
|
8450
8755
|
}
|
|
8451
|
-
Statement::ForOf { iterable, body, .. }
|
|
8756
|
+
Statement::ForOf { iterable, body, .. }
|
|
8757
|
+
| Statement::ForIn {
|
|
8758
|
+
object: iterable,
|
|
8759
|
+
body,
|
|
8760
|
+
..
|
|
8761
|
+
} => {
|
|
8452
8762
|
Self::expr_changes_base_len(iterable, base) || Self::stmt_changes_base_len(body, base)
|
|
8453
8763
|
}
|
|
8454
8764
|
Statement::While { cond, body, .. } | Statement::DoWhile { body, cond, .. } => {
|
|
@@ -8857,7 +9167,12 @@ impl Codegen {
|
|
|
8857
9167
|
}
|
|
8858
9168
|
Self::collect_captured_block_vars_from_statements(body, block_vars, result);
|
|
8859
9169
|
}
|
|
8860
|
-
Statement::ForOf { iterable, body, .. }
|
|
9170
|
+
Statement::ForOf { iterable, body, .. }
|
|
9171
|
+
| Statement::ForIn {
|
|
9172
|
+
object: iterable,
|
|
9173
|
+
body,
|
|
9174
|
+
..
|
|
9175
|
+
} => {
|
|
8861
9176
|
Self::collect_captured_block_vars_from_expr(iterable, block_vars, result);
|
|
8862
9177
|
Self::collect_captured_block_vars_from_statements(body, block_vars, result);
|
|
8863
9178
|
}
|
|
@@ -8995,7 +9310,8 @@ impl Codegen {
|
|
|
8995
9310
|
}
|
|
8996
9311
|
Self::collect_local_var_names(body, names);
|
|
8997
9312
|
}
|
|
8998
|
-
Statement::ForOf { body, .. }
|
|
9313
|
+
Statement::ForOf { body, .. }
|
|
9314
|
+
| Statement::ForIn { body, .. } => Self::collect_local_var_names(body, names),
|
|
8999
9315
|
Statement::While { body, .. } | Statement::DoWhile { body, .. } => {
|
|
9000
9316
|
Self::collect_local_var_names(body, names);
|
|
9001
9317
|
}
|
|
@@ -9261,7 +9577,12 @@ impl Codegen {
|
|
|
9261
9577
|
}
|
|
9262
9578
|
Self::collect_stmt_idents(body, idents);
|
|
9263
9579
|
}
|
|
9264
|
-
Statement::ForOf { iterable, body, .. }
|
|
9580
|
+
Statement::ForOf { iterable, body, .. }
|
|
9581
|
+
| Statement::ForIn {
|
|
9582
|
+
object: iterable,
|
|
9583
|
+
body,
|
|
9584
|
+
..
|
|
9585
|
+
} => {
|
|
9265
9586
|
Self::collect_expr_idents(iterable, idents);
|
|
9266
9587
|
Self::collect_stmt_idents(body, idents);
|
|
9267
9588
|
}
|
|
@@ -12150,6 +12471,18 @@ impl Codegen {
|
|
|
12150
12471
|
}
|
|
12151
12472
|
_ => false,
|
|
12152
12473
|
},
|
|
12474
|
+
// `Math.imul(a, b)` — an exact int32-domain node (emits an i32 `wrapping_mul`, see
|
|
12475
|
+
// `emit_int32_operand`), so it participates in an int32 chain when both operands are
|
|
12476
|
+
// themselves int32-lowerable. Lets `h = Math.imul(h, K) >>> 0` keep `h` in a register
|
|
12477
|
+
// across the loop instead of narrowing/widening `h` on every op.
|
|
12478
|
+
Expr::Call { callee, args, .. } => {
|
|
12479
|
+
matches!(callee.as_ref(),
|
|
12480
|
+
Expr::Member { object, prop: MemberProp::Name { name: m, .. }, .. }
|
|
12481
|
+
if m.as_ref() == "imul"
|
|
12482
|
+
&& matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Math"))
|
|
12483
|
+
&& matches!(args.as_slice(), [CallArg::Expr(a0), CallArg::Expr(a1)]
|
|
12484
|
+
if self.i32_chain_lowerable(a0, var) && self.i32_chain_lowerable(a1, var))
|
|
12485
|
+
}
|
|
12153
12486
|
_ => false,
|
|
12154
12487
|
}
|
|
12155
12488
|
}
|
|
@@ -12391,6 +12724,7 @@ impl Codegen {
|
|
|
12391
12724
|
}
|
|
12392
12725
|
Statement::For { body, .. }
|
|
12393
12726
|
| Statement::ForOf { body, .. }
|
|
12727
|
+
| Statement::ForIn { body, .. }
|
|
12394
12728
|
| Statement::While { body, .. }
|
|
12395
12729
|
| Statement::DoWhile { body, .. } => {
|
|
12396
12730
|
Self::collect_ordered_assigns_to(body, var, out)
|
|
@@ -12415,6 +12749,7 @@ impl Codegen {
|
|
|
12415
12749
|
}
|
|
12416
12750
|
Statement::For { body, .. }
|
|
12417
12751
|
| Statement::ForOf { body, .. }
|
|
12752
|
+
| Statement::ForIn { body, .. }
|
|
12418
12753
|
| Statement::While { body, .. }
|
|
12419
12754
|
| Statement::DoWhile { body, .. }
|
|
12420
12755
|
| Statement::FunDecl { body, .. } => f(std::slice::from_ref(body)),
|
|
@@ -12480,7 +12815,12 @@ impl Codegen {
|
|
|
12480
12815
|
}
|
|
12481
12816
|
Self::for_each_stmt_expr(body, f);
|
|
12482
12817
|
}
|
|
12483
|
-
Statement::ForOf { iterable, body, .. }
|
|
12818
|
+
Statement::ForOf { iterable, body, .. }
|
|
12819
|
+
| Statement::ForIn {
|
|
12820
|
+
object: iterable,
|
|
12821
|
+
body,
|
|
12822
|
+
..
|
|
12823
|
+
} => {
|
|
12484
12824
|
f(iterable);
|
|
12485
12825
|
Self::for_each_stmt_expr(body, f);
|
|
12486
12826
|
}
|
|
@@ -12893,7 +13233,11 @@ impl Codegen {
|
|
|
12893
13233
|
Self::collect_annotated_types(std::slice::from_ref(e), aliases, env);
|
|
12894
13234
|
}
|
|
12895
13235
|
}
|
|
12896
|
-
|
|
13236
|
+
// for-in's loop var is always a string key, so it gets no Vec-element type binding —
|
|
13237
|
+
// it behaves like while/do-while here (recurse the body only).
|
|
13238
|
+
Statement::While { body, .. }
|
|
13239
|
+
| Statement::DoWhile { body, .. }
|
|
13240
|
+
| Statement::ForIn { body, .. } => {
|
|
12897
13241
|
Self::collect_annotated_types(std::slice::from_ref(body), aliases, env)
|
|
12898
13242
|
}
|
|
12899
13243
|
Statement::ForOf {
|
|
@@ -13037,7 +13381,12 @@ impl Codegen {
|
|
|
13037
13381
|
}
|
|
13038
13382
|
Self::collect_reassignments_stmt(body, out);
|
|
13039
13383
|
}
|
|
13040
|
-
Statement::ForOf { iterable, body, .. }
|
|
13384
|
+
Statement::ForOf { iterable, body, .. }
|
|
13385
|
+
| Statement::ForIn {
|
|
13386
|
+
object: iterable,
|
|
13387
|
+
body,
|
|
13388
|
+
..
|
|
13389
|
+
} => {
|
|
13041
13390
|
Self::collect_reassignments_expr(iterable, out);
|
|
13042
13391
|
Self::collect_reassignments_stmt(body, out);
|
|
13043
13392
|
}
|
|
@@ -13299,6 +13648,22 @@ impl Codegen {
|
|
|
13299
13648
|
}
|
|
13300
13649
|
}
|
|
13301
13650
|
}
|
|
13651
|
+
// Two-arg `Math.imul(a, b)` → an exact 32-bit product (a number). Modelled so a value
|
|
13652
|
+
// fed by it (`h = Math.imul(h, K) >>> 0`) is not demoted to a boxed `Value`.
|
|
13653
|
+
if let [CallArg::Expr(_), CallArg::Expr(_)] = args.as_slice() {
|
|
13654
|
+
if let Expr::Member {
|
|
13655
|
+
object,
|
|
13656
|
+
prop: MemberProp::Name { name: method, .. },
|
|
13657
|
+
..
|
|
13658
|
+
} = callee.as_ref()
|
|
13659
|
+
{
|
|
13660
|
+
if method.as_ref() == "imul"
|
|
13661
|
+
&& matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Math")
|
|
13662
|
+
{
|
|
13663
|
+
return RustType::F64;
|
|
13664
|
+
}
|
|
13665
|
+
}
|
|
13666
|
+
}
|
|
13302
13667
|
// #169: a fused native `Vec<f64>` reduce produces an `f64` (the emitter lowers it via
|
|
13303
13668
|
// `native_vec_hof_for_call`). Model it here so an accumulator fed by `xs.reduce(...)`
|
|
13304
13669
|
// is not wrongly demoted to a boxed `Value`. Conservative: any miss → `Value`.
|
|
@@ -13984,7 +14349,16 @@ impl Codegen {
|
|
|
13984
14349
|
self.emit_native_fn_body(&statements[i])?;
|
|
13985
14350
|
} else {
|
|
13986
14351
|
self.emit_statement(&statements[i])?;
|
|
13987
|
-
|
|
14352
|
+
// Same guard as the primary emit site above: a native-typed frame
|
|
14353
|
+
// (`-> f64`/`-> Vec<..>`/`-> ()`) can't type the `Err`/`Value::Null` check, so
|
|
14354
|
+
// suppress it there — the parked throw still surfaces at the first Value/Result
|
|
14355
|
+
// frame up the chain. Without this guard the strict-`<` fast-path emitted an
|
|
14356
|
+
// untypeable checkpoint after any may-throw stmt (e.g. an index read post-#425),
|
|
14357
|
+
// breaking pure-numeric fixtures like fannkuch.
|
|
14358
|
+
if Self::stmt_may_throw(&statements[i])
|
|
14359
|
+
&& !Self::is_terminator_stmt(&statements[i])
|
|
14360
|
+
&& (self.try_closure_depth > 0 || !self.in_native_typed_frame())
|
|
14361
|
+
{
|
|
13988
14362
|
self.emit_pending_throw_check();
|
|
13989
14363
|
}
|
|
13990
14364
|
}
|
|
@@ -14115,7 +14489,8 @@ impl Codegen {
|
|
|
14115
14489
|
}
|
|
14116
14490
|
Self::walk_stmt_for_module_const_disqualifiers(body, g, bad);
|
|
14117
14491
|
}
|
|
14118
|
-
Statement::ForOf { body, .. }
|
|
14492
|
+
Statement::ForOf { body, .. }
|
|
14493
|
+
| Statement::ForIn { body, .. } => {
|
|
14119
14494
|
Self::walk_stmt_for_module_const_disqualifiers(body, g, bad);
|
|
14120
14495
|
}
|
|
14121
14496
|
Statement::Switch {
|
|
@@ -14536,6 +14911,32 @@ impl Codegen {
|
|
|
14536
14911
|
)
|
|
14537
14912
|
}
|
|
14538
14913
|
|
|
14914
|
+
fn expr_is_set_construct(expr: &Expr) -> bool {
|
|
14915
|
+
matches!(
|
|
14916
|
+
expr,
|
|
14917
|
+
Expr::New {
|
|
14918
|
+
callee,
|
|
14919
|
+
..
|
|
14920
|
+
} if matches!(callee.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Set")
|
|
14921
|
+
)
|
|
14922
|
+
}
|
|
14923
|
+
|
|
14924
|
+
/// #456 regression guard: should `<object>.keys()/.values()/.entries()` DEFER to the generic
|
|
14925
|
+
/// bound-method call instead of the array fast-path? True when `object` is not an array:
|
|
14926
|
+
/// - the `Object` global (`Object.keys/values/entries` are STATICS → `tish_object_*`),
|
|
14927
|
+
/// - a known `new Map()`/`new Set()` local, or a direct `new Map(…)`/`new Set(…)` receiver
|
|
14928
|
+
/// (their keys/values/entries resolve as bound methods via `get_prop`).
|
|
14929
|
+
/// Arrays (everything else) keep the fast-path — they have no runtime method table.
|
|
14930
|
+
fn kve_defers_to_generic(&self, object: &Expr) -> bool {
|
|
14931
|
+
match object {
|
|
14932
|
+
Expr::Ident { name, .. } => {
|
|
14933
|
+
let n = name.as_ref();
|
|
14934
|
+
n == "Object" || self.collection_instance_locals.contains(n)
|
|
14935
|
+
}
|
|
14936
|
+
_ => Self::expr_is_map_construct(object) || Self::expr_is_set_construct(object),
|
|
14937
|
+
}
|
|
14938
|
+
}
|
|
14939
|
+
|
|
14539
14940
|
/// #176 — classify top-level `let x = <number-literal>` bindings whose every use is a numeric
|
|
14540
14941
|
/// read or a whole-binding assign with a numeric-shaped RHS (fasta `seed`).
|
|
14541
14942
|
fn collect_native_numeric_globals(stmts: &[Statement]) -> HashMap<String, f64> {
|
|
@@ -14865,7 +15266,8 @@ impl Codegen {
|
|
|
14865
15266
|
Statement::While { body, .. }
|
|
14866
15267
|
| Statement::DoWhile { body, .. }
|
|
14867
15268
|
| Statement::For { body, .. }
|
|
14868
|
-
| Statement::ForOf { body, .. }
|
|
15269
|
+
| Statement::ForOf { body, .. }
|
|
15270
|
+
| Statement::ForIn { body, .. } => {
|
|
14869
15271
|
Self::walk_stmt_for_global_disqualifiers(body, g, bad);
|
|
14870
15272
|
}
|
|
14871
15273
|
Statement::Switch {
|
|
@@ -15105,7 +15507,12 @@ impl Codegen {
|
|
|
15105
15507
|
f(cond);
|
|
15106
15508
|
Self::for_each_expr_root_stmt(body, f);
|
|
15107
15509
|
}
|
|
15108
|
-
Statement::ForOf { iterable, body, .. }
|
|
15510
|
+
Statement::ForOf { iterable, body, .. }
|
|
15511
|
+
| Statement::ForIn {
|
|
15512
|
+
object: iterable,
|
|
15513
|
+
body,
|
|
15514
|
+
..
|
|
15515
|
+
} => {
|
|
15109
15516
|
f(iterable);
|
|
15110
15517
|
Self::for_each_expr_root_stmt(body, f);
|
|
15111
15518
|
}
|
|
@@ -15717,12 +16124,16 @@ impl Codegen {
|
|
|
15717
16124
|
}
|
|
15718
16125
|
Expr::Member { object, prop: MemberProp::Name { name: m, .. }, .. } => {
|
|
15719
16126
|
matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Math")
|
|
15720
|
-
&& args.len() == 1
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
16127
|
+
&& ((args.len() == 1
|
|
16128
|
+
&& matches!(
|
|
16129
|
+
m.as_ref(),
|
|
16130
|
+
"sqrt" | "sin" | "cos" | "tan" | "abs" | "floor" | "ceil"
|
|
16131
|
+
| "exp" | "trunc" | "log"
|
|
16132
|
+
))
|
|
16133
|
+
// 2-arg `Math.imul` inlines to an exact i32 wrapping multiply — a
|
|
16134
|
+
// native-safe numeric primitive (no dispatch), so a fn using it (FNV
|
|
16135
|
+
// with imul, xmur3/mulberry32 PRNGs) stays eligible for native typing.
|
|
16136
|
+
|| (args.len() == 2 && m.as_ref() == "imul"))
|
|
15726
16137
|
}
|
|
15727
16138
|
_ => false,
|
|
15728
16139
|
}
|
|
@@ -18325,7 +18736,8 @@ impl Codegen {
|
|
|
18325
18736
|
Statement::While { body, .. }
|
|
18326
18737
|
| Statement::DoWhile { body, .. }
|
|
18327
18738
|
| Statement::For { body, .. }
|
|
18328
|
-
| Statement::ForOf { body, .. }
|
|
18739
|
+
| Statement::ForOf { body, .. }
|
|
18740
|
+
| Statement::ForIn { body, .. } => Self::rec_collect_returns(body, out),
|
|
18329
18741
|
_ => {}
|
|
18330
18742
|
}
|
|
18331
18743
|
}
|
|
@@ -18469,7 +18881,8 @@ impl Codegen {
|
|
|
18469
18881
|
}
|
|
18470
18882
|
Statement::While { body, .. }
|
|
18471
18883
|
| Statement::DoWhile { body, .. }
|
|
18472
|
-
| Statement::ForOf { body, .. }
|
|
18884
|
+
| Statement::ForOf { body, .. }
|
|
18885
|
+
| Statement::ForIn { body, .. } => Self::rec_orch_binds_node(body, builders),
|
|
18473
18886
|
Statement::For { init, body, .. } => {
|
|
18474
18887
|
init.as_ref()
|
|
18475
18888
|
.is_some_and(|i| Self::rec_orch_binds_node(i, builders))
|
|
@@ -20553,7 +20966,8 @@ impl Codegen {
|
|
|
20553
20966
|
}
|
|
20554
20967
|
Statement::While { body, .. }
|
|
20555
20968
|
| Statement::DoWhile { body, .. }
|
|
20556
|
-
| Statement::ForOf { body, .. }
|
|
20969
|
+
| Statement::ForOf { body, .. }
|
|
20970
|
+
| Statement::ForIn { body, .. } => Self::agg_collect_aliases(body, p, out),
|
|
20557
20971
|
_ => {}
|
|
20558
20972
|
}
|
|
20559
20973
|
}
|
|
@@ -20581,7 +20995,8 @@ impl Codegen {
|
|
|
20581
20995
|
Statement::For { body, .. }
|
|
20582
20996
|
| Statement::While { body, .. }
|
|
20583
20997
|
| Statement::DoWhile { body, .. }
|
|
20584
|
-
| Statement::ForOf { body, .. }
|
|
20998
|
+
| Statement::ForOf { body, .. }
|
|
20999
|
+
| Statement::ForIn { body, .. } => Self::agg_stmt_writes(body, p, aliases),
|
|
20585
21000
|
_ => false,
|
|
20586
21001
|
}
|
|
20587
21002
|
}
|
|
@@ -20705,7 +21120,8 @@ impl Codegen {
|
|
|
20705
21120
|
Statement::For { body, .. }
|
|
20706
21121
|
| Statement::While { body, .. }
|
|
20707
21122
|
| Statement::DoWhile { body, .. }
|
|
20708
|
-
| Statement::ForOf { body, .. }
|
|
21123
|
+
| Statement::ForOf { body, .. }
|
|
21124
|
+
| Statement::ForIn { body, .. } => Self::stmt_returns_value(body),
|
|
20709
21125
|
_ => false,
|
|
20710
21126
|
}
|
|
20711
21127
|
}
|
|
@@ -20764,6 +21180,30 @@ impl Codegen {
|
|
|
20764
21180
|
return Ok(Some(code));
|
|
20765
21181
|
}
|
|
20766
21182
|
}
|
|
21183
|
+
// `Math.imul(a, b)` in an int32 context → the raw i32 `wrapping_mul` (no f64 excursion, no
|
|
21184
|
+
// `to_int32` round-trip on the product), when both operands are themselves int32-lowerable.
|
|
21185
|
+
// JS ES6 semantics (ToInt32 both operands, wrapping 32-bit multiply, signed result) are carried
|
|
21186
|
+
// by the operands' own int32 lowering. Keeps `h = Math.imul(h, K) >>> 0` fully register-domain.
|
|
21187
|
+
if let Expr::Call { callee, args, .. } = e {
|
|
21188
|
+
if let [CallArg::Expr(a0), CallArg::Expr(a1)] = args.as_slice() {
|
|
21189
|
+
if let Expr::Member {
|
|
21190
|
+
object,
|
|
21191
|
+
prop: MemberProp::Name { name: m, .. },
|
|
21192
|
+
..
|
|
21193
|
+
} = callee.as_ref()
|
|
21194
|
+
{
|
|
21195
|
+
if m.as_ref() == "imul"
|
|
21196
|
+
&& matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Math")
|
|
21197
|
+
{
|
|
21198
|
+
let ai = self.emit_int32_operand(a0)?;
|
|
21199
|
+
let bi = self.emit_int32_operand(a1)?;
|
|
21200
|
+
if let (Some(ai), Some(bi)) = (ai, bi) {
|
|
21201
|
+
return Ok(Some(format!("(({}).wrapping_mul({}))", ai, bi)));
|
|
21202
|
+
}
|
|
21203
|
+
}
|
|
21204
|
+
}
|
|
21205
|
+
}
|
|
21206
|
+
}
|
|
20767
21207
|
// Integer-literal leaf: `ToInt32(<int literal>)` is a compile-time constant — emit it
|
|
20768
21208
|
// directly (`v as i32` = modulo 2^32 = JS ToInt32 of the integer) instead of a runtime
|
|
20769
21209
|
// `to_int32(1_f64)` call. Removes the constant round-trip in masks/shift-counts
|
|
@@ -21334,6 +21774,35 @@ impl Codegen {
|
|
|
21334
21774
|
}
|
|
21335
21775
|
}
|
|
21336
21776
|
}
|
|
21777
|
+
// `Math.imul(a, b)` → an inline exact 32-bit wrapping multiply (ES6: ToInt32 both
|
|
21778
|
+
// operands, wrapping i32 multiply, signed int32 result). Both args go through
|
|
21779
|
+
// `emit_int32_operand` — the SAME ToInt32 lowering the bitwise path uses — so this is
|
|
21780
|
+
// bit-for-bit identical to the boxed `tish_math_imul` and to V8: typed==boxed==node
|
|
21781
|
+
// holds. It eliminates the per-call arg boxing + `value_call` a hot hashing/PRNG loop
|
|
21782
|
+
// (xmur3, mulberry32, FNV-with-imul) would otherwise pay. A general primitive (fires
|
|
21783
|
+
// for any `Math.imul`), not a fixture-specific kernel emission (cf. #317). Falls
|
|
21784
|
+
// through to the boxed call when an operand isn't int32-lowerable.
|
|
21785
|
+
if let [CallArg::Expr(a0), CallArg::Expr(a1)] = args.as_slice() {
|
|
21786
|
+
if let Expr::Member {
|
|
21787
|
+
object,
|
|
21788
|
+
prop: MemberProp::Name { name: method, .. },
|
|
21789
|
+
..
|
|
21790
|
+
} = callee.as_ref()
|
|
21791
|
+
{
|
|
21792
|
+
if method.as_ref() == "imul"
|
|
21793
|
+
&& matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Math")
|
|
21794
|
+
{
|
|
21795
|
+
let ai = self.emit_int32_operand(a0)?;
|
|
21796
|
+
let bi = self.emit_int32_operand(a1)?;
|
|
21797
|
+
if let (Some(ai), Some(bi)) = (ai, bi) {
|
|
21798
|
+
return Ok((
|
|
21799
|
+
format!("(({}).wrapping_mul({}) as f64)", ai, bi),
|
|
21800
|
+
RustType::F64,
|
|
21801
|
+
));
|
|
21802
|
+
}
|
|
21803
|
+
}
|
|
21804
|
+
}
|
|
21805
|
+
}
|
|
21337
21806
|
if let [CallArg::Expr(arg_expr)] = args.as_slice() {
|
|
21338
21807
|
if let Expr::Member {
|
|
21339
21808
|
object,
|