From 392d09279db8e8daabc591094a15d635d8f8ea30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 30 May 2026 20:57:25 +0200 Subject: [PATCH] fix(array): honor fromIndex/start/end/depth in indexOf/fill/flat/copyWithin #2804: thread the optional fromIndex through Array/TypedArray indexOf and includes (runtime helpers + static codegen + dynamic dispatch). #2801: honor start/end in dynamic Array.prototype.fill dispatch. #2800: honor depth in dynamic Array.prototype.flat dispatch. #2802: add dynamic Array.prototype.copyWithin dispatch. Also fixes a pre-existing duplicate stable-hash tag (12045) shared by RegExpEscape and ReflectIsExtensible that broke cargo test -p perry-hir. --- .../perry-codegen-js/src/emit/exprs_more.rs | 20 +++- .../src/emit/expr/arrays.rs | 14 ++- .../src/emit/string_collection.rs | 14 ++- .../src/collectors/escape_check.rs | 14 ++- .../src/collectors/escape_news.rs | 14 ++- .../src/collectors/i32_locals.rs | 9 +- crates/perry-codegen/src/collectors/refs.rs | 18 ++- .../perry-codegen/src/expr/instance_misc1.rs | 19 +++- crates/perry-codegen/src/expr/misc_methods.rs | 19 +++- .../perry-codegen/src/lower_array_method.rs | 36 +++++- .../src/runtime_decls/strings.rs | 9 +- crates/perry-hir/src/analysis.rs | 2 +- crates/perry-hir/src/ir/expr.rs | 6 +- crates/perry-hir/src/js_transform/imports.rs | 14 ++- .../src/lower/expr_call/array_only_methods.rs | 14 ++- .../lower/expr_call/imported_array_methods.rs | 13 ++- .../lower/expr_call/inline_array_methods.rs | 13 ++- .../lower/expr_call/local_array_methods.rs | 13 ++- crates/perry-hir/src/monomorph/driver.rs | 14 ++- .../src/monomorph/substitute_expr.rs | 18 ++- .../src/monomorph/update_call_sites.rs | 14 ++- crates/perry-hir/src/stable_hash/expr.rs | 6 +- crates/perry-hir/src/walker/expr_mut.rs | 14 ++- crates/perry-hir/src/walker/expr_ref.rs | 14 ++- crates/perry-runtime/src/array/search.rs | 105 ++++++++++++++---- .../src/object/native_call_method.rs | 64 ++++++++++- ...t_gap_array_optargs_2804_2801_2800_2802.ts | 89 +++++++++++++++ 27 files changed, 515 insertions(+), 84 deletions(-) create mode 100644 test-files/test_gap_array_optargs_2804_2801_2800_2802.ts diff --git a/crates/perry-codegen-js/src/emit/exprs_more.rs b/crates/perry-codegen-js/src/emit/exprs_more.rs index 0ffa5662bd..821553f86f 100644 --- a/crates/perry-codegen-js/src/emit/exprs_more.rs +++ b/crates/perry-codegen-js/src/emit/exprs_more.rs @@ -115,16 +115,32 @@ impl JsEmitter { self.emit_expr(value); self.output.push(')'); } - Expr::ArrayIndexOf { array, value } => { + Expr::ArrayIndexOf { + array, + value, + from_index, + } => { self.emit_expr(array); self.output.push_str(".indexOf("); self.emit_expr(value); + if let Some(fi) = from_index { + self.output.push_str(", "); + self.emit_expr(fi); + } self.output.push(')'); } - Expr::ArrayIncludes { array, value } => { + Expr::ArrayIncludes { + array, + value, + from_index, + } => { self.emit_expr(array); self.output.push_str(".includes("); self.emit_expr(value); + if let Some(fi) = from_index { + self.output.push_str(", "); + self.emit_expr(fi); + } self.output.push(')'); } Expr::ArraySlice { array, start, end } => { diff --git a/crates/perry-codegen-wasm/src/emit/expr/arrays.rs b/crates/perry-codegen-wasm/src/emit/expr/arrays.rs index 3da7a0cd9b..70ab6514bb 100644 --- a/crates/perry-codegen-wasm/src/emit/expr/arrays.rs +++ b/crates/perry-codegen-wasm/src/emit/expr/arrays.rs @@ -264,13 +264,23 @@ impl<'a> FuncEmitCtx<'a> { })); self.emit_memcall(func, "array_join", 2); } - Expr::ArrayIndexOf { array, value } => { + Expr::ArrayIndexOf { + array, + value, + from_index: _, + } => { + // NOTE: the wasm backend's array_index_of helper does not yet + // honor the optional fromIndex (#2804 covers the native path). self.emit_frame_begin(func, 2); self.emit_store_arg(func, 0, array); self.emit_store_arg(func, 1, value); self.emit_memcall(func, "array_index_of", 2); } - Expr::ArrayIncludes { array, value } => { + Expr::ArrayIncludes { + array, + value, + from_index: _, + } => { self.emit_frame_begin(func, 2); self.emit_store_arg(func, 0, array); self.emit_store_arg(func, 1, value); diff --git a/crates/perry-codegen-wasm/src/emit/string_collection.rs b/crates/perry-codegen-wasm/src/emit/string_collection.rs index 35c8d20bd3..a1cb19067e 100644 --- a/crates/perry-codegen-wasm/src/emit/string_collection.rs +++ b/crates/perry-codegen-wasm/src/emit/string_collection.rs @@ -674,9 +674,21 @@ impl WasmModuleEmitter { } self.intern_string(","); // default separator } - Expr::ArrayIndexOf { array, value } | Expr::ArrayIncludes { array, value } => { + Expr::ArrayIndexOf { + array, + value, + from_index, + } + | Expr::ArrayIncludes { + array, + value, + from_index, + } => { self.collect_strings_in_expr(array); self.collect_strings_in_expr(value); + if let Some(fi) = from_index { + self.collect_strings_in_expr(fi); + } } Expr::ArrayMap { array, callback } | Expr::ArrayFilter { array, callback } diff --git a/crates/perry-codegen/src/collectors/escape_check.rs b/crates/perry-codegen/src/collectors/escape_check.rs index 35f986e22b..9d24aa8bbd 100644 --- a/crates/perry-codegen/src/collectors/escape_check.rs +++ b/crates/perry-codegen/src/collectors/escape_check.rs @@ -594,11 +594,17 @@ pub fn check_escapes_in_expr( check_escapes_in_expr(e, candidates, classes, escaped); } } - Expr::ArrayIncludes { array, value } | Expr::ArrayIndexOf { array, value } => { - check_escapes_in_expr(array, candidates, classes, escaped); - check_escapes_in_expr(value, candidates, classes, escaped); + Expr::ArrayIncludes { + array, + value, + from_index, + } + | Expr::ArrayIndexOf { + array, + value, + from_index, } - Expr::ArrayLastIndexOf { + | Expr::ArrayLastIndexOf { array, value, from_index, diff --git a/crates/perry-codegen/src/collectors/escape_news.rs b/crates/perry-codegen/src/collectors/escape_news.rs index 0f5a9d872c..8bd5938c26 100644 --- a/crates/perry-codegen/src/collectors/escape_news.rs +++ b/crates/perry-codegen/src/collectors/escape_news.rs @@ -454,11 +454,17 @@ fn collect_used_new_fields_in_expr( collect_used_new_fields_in_expr(end, non_escaping_news, used); } } - Expr::ArrayIncludes { array, value } | Expr::ArrayIndexOf { array, value } => { - collect_used_new_fields_in_expr(array, non_escaping_news, used); - collect_used_new_fields_in_expr(value, non_escaping_news, used); + Expr::ArrayIncludes { + array, + value, + from_index, + } + | Expr::ArrayIndexOf { + array, + value, + from_index, } - Expr::ArrayLastIndexOf { + | Expr::ArrayLastIndexOf { array, value, from_index, diff --git a/crates/perry-codegen/src/collectors/i32_locals.rs b/crates/perry-codegen/src/collectors/i32_locals.rs index 9736b3ac6e..1566ab1b8b 100644 --- a/crates/perry-codegen/src/collectors/i32_locals.rs +++ b/crates/perry-codegen/src/collectors/i32_locals.rs @@ -1285,9 +1285,16 @@ pub fn collect_localset_ids_in_expr_filtered( walk(e, out); } } - Expr::ArrayIncludes { array, value } => { + Expr::ArrayIncludes { + array, + value, + from_index, + } => { walk(array, out); walk(value, out); + if let Some(fi) = from_index { + walk(fi, out); + } } Expr::Object(props) => { for (_, v) in props { diff --git a/crates/perry-codegen/src/collectors/refs.rs b/crates/perry-codegen/src/collectors/refs.rs index 2e2f250c5f..c79713fe1f 100644 --- a/crates/perry-codegen/src/collectors/refs.rs +++ b/crates/perry-codegen/src/collectors/refs.rs @@ -397,9 +397,16 @@ pub fn collect_ref_ids_in_expr(e: &perry_hir::Expr, out: &mut HashSet) { walk(e, out); } } - Expr::ArrayIncludes { array, value } => { + Expr::ArrayIncludes { + array, + value, + from_index, + } => { walk(array, out); walk(value, out); + if let Some(fi) = from_index { + walk(fi, out); + } } Expr::Object(props) => { for (_, v) in props { @@ -719,11 +726,12 @@ pub fn collect_ref_ids_in_expr(e: &perry_hir::Expr, out: &mut HashSet) { out.insert(*array_id); walk(source, out); } - Expr::ArrayIndexOf { array, value } => { - walk(array, out); - walk(value, out); + Expr::ArrayIndexOf { + array, + value, + from_index, } - Expr::ArrayLastIndexOf { + | Expr::ArrayLastIndexOf { array, value, from_index, diff --git a/crates/perry-codegen/src/expr/instance_misc1.rs b/crates/perry-codegen/src/expr/instance_misc1.rs index 2d53f2b04b..a4291e6ec1 100644 --- a/crates/perry-codegen/src/expr/instance_misc1.rs +++ b/crates/perry-codegen/src/expr/instance_misc1.rs @@ -633,9 +633,19 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { } // -------- arr.includes(value) -> boolean -------- - Expr::ArrayIncludes { array, value } => { + Expr::ArrayIncludes { + array, + value, + from_index, + } => { let arr_box = lower_expr(ctx, array)?; let v = lower_expr(ctx, value)?; + // #2804: optional fromIndex. has_from=1 + lowered index when + // present; otherwise has_from=0 with a placeholder DOUBLE (`v`). + let (from_box, has_from) = match from_index { + Some(fi) => (lower_expr(ctx, fi)?, "1"), + None => (v.clone(), "0"), + }; let blk = ctx.block(); let arr_handle = unbox_to_i64(blk, &arr_box); // Use `js_array_includes_jsvalue` which does deep-value @@ -645,7 +655,12 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { let i32_v = blk.call( I32, "js_array_includes_jsvalue", - &[(I64, &arr_handle), (DOUBLE, &v)], + &[ + (I64, &arr_handle), + (DOUBLE, &v), + (DOUBLE, &from_box), + (I32, has_from), + ], ); // Convert i32 boolean to NaN-tagged TAG_TRUE/FALSE so // console.log prints "true"/"false". diff --git a/crates/perry-codegen/src/expr/misc_methods.rs b/crates/perry-codegen/src/expr/misc_methods.rs index f9a81d456e..68550722ce 100644 --- a/crates/perry-codegen/src/expr/misc_methods.rs +++ b/crates/perry-codegen/src/expr/misc_methods.rs @@ -467,15 +467,30 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // match by content (handles SSO + heap-string mixed arrays). // Mirrors the `includes` arm + the `lower_array_method::indexOf` // arm. - Expr::ArrayIndexOf { array, value } => { + Expr::ArrayIndexOf { + array, + value, + from_index, + } => { let arr_box = lower_expr(ctx, array)?; let v = lower_expr(ctx, value)?; + // #2804: optional fromIndex. has_from=1 + lowered index when + // present; otherwise has_from=0 with a placeholder DOUBLE (`v`). + let (from_box, has_from) = match from_index { + Some(fi) => (lower_expr(ctx, fi)?, "1"), + None => (v.clone(), "0"), + }; let blk = ctx.block(); let arr_handle = unbox_to_i64(blk, &arr_box); let i32_v = blk.call( I32, "js_array_indexOf_jsvalue", - &[(I64, &arr_handle), (DOUBLE, &v)], + &[ + (I64, &arr_handle), + (DOUBLE, &v), + (DOUBLE, &from_box), + (I32, has_from), + ], ); Ok(blk.sitofp(I32, &i32_v, DOUBLE)) } diff --git a/crates/perry-codegen/src/lower_array_method.rs b/crates/perry-codegen/src/lower_array_method.rs index 2fa6930184..4ec94336bf 100644 --- a/crates/perry-codegen/src/lower_array_method.rs +++ b/crates/perry-codegen/src/lower_array_method.rs @@ -410,13 +410,20 @@ pub(crate) fn lower_array_method( Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))) } "includes" => { - if args.len() != 1 { + if args.is_empty() || args.len() > 2 { bail!( - "perry-codegen: Array.includes expects 1 arg, got {}", + "perry-codegen: Array.includes expects 1-2 args, got {}", args.len() ); } let val_box = lower_expr(ctx, &args[0])?; + // #2804: optional fromIndex (2nd arg). has_from=1 + lowered index + // when present; otherwise has_from=0 with a placeholder DOUBLE. + let (from_box, has_from) = if args.len() == 2 { + (lower_expr(ctx, &args[1])?, "1") + } else { + (val_box.clone(), "0") + }; let blk = ctx.block(); let recv_handle = unbox_to_i64(blk, &recv_box); // Use `js_array_includes_jsvalue` for deep equality so @@ -427,7 +434,12 @@ pub(crate) fn lower_array_method( let i32_v = blk.call( I32, "js_array_includes_jsvalue", - &[(I64, &recv_handle), (DOUBLE, &val_box)], + &[ + (I64, &recv_handle), + (DOUBLE, &val_box), + (DOUBLE, &from_box), + (I32, has_from), + ], ); // Convert i32 boolean to NaN-boxed true/false let bit = blk.icmp_ne(I32, &i32_v, "0"); @@ -441,13 +453,20 @@ pub(crate) fn lower_array_method( Ok(blk.bitcast_i64_to_double(&tagged)) } "indexOf" => { - if args.len() != 1 { + if args.is_empty() || args.len() > 2 { bail!( - "perry-codegen: Array.indexOf expects 1 arg, got {}", + "perry-codegen: Array.indexOf expects 1-2 args, got {}", args.len() ); } let val_box = lower_expr(ctx, &args[0])?; + // #2804: optional fromIndex (2nd arg). has_from=1 + lowered index + // when present; otherwise has_from=0 with a placeholder DOUBLE. + let (from_box, has_from) = if args.len() == 2 { + (lower_expr(ctx, &args[1])?, "1") + } else { + (val_box.clone(), "0") + }; let blk = ctx.block(); let recv_handle = unbox_to_i64(blk, &recv_box); // Issue #214: route through `_jsvalue` so string elements @@ -459,7 +478,12 @@ pub(crate) fn lower_array_method( let i32_v = blk.call( I32, "js_array_indexOf_jsvalue", - &[(I64, &recv_handle), (DOUBLE, &val_box)], + &[ + (I64, &recv_handle), + (DOUBLE, &val_box), + (DOUBLE, &from_box), + (I32, has_from), + ], ); Ok(blk.sitofp(I32, &i32_v, DOUBLE)) } diff --git a/crates/perry-codegen/src/runtime_decls/strings.rs b/crates/perry-codegen/src/runtime_decls/strings.rs index 50cbd12677..486933d61b 100644 --- a/crates/perry-codegen/src/runtime_decls/strings.rs +++ b/crates/perry-codegen/src/runtime_decls/strings.rs @@ -721,14 +721,19 @@ pub fn declare_phase_b_strings(module: &mut LlModule) { module.declare_function("js_date_new_from_timestamp", DOUBLE, &[DOUBLE]); module.declare_function("js_date_new_from_value", DOUBLE, &[DOUBLE]); module.declare_function("js_array_indexOf_f64", I32, &[I64, DOUBLE]); - module.declare_function("js_array_indexOf_jsvalue", I32, &[I64, DOUBLE]); + // #2804: indexOf/includes carry an optional fromIndex (value, fromIndex, has_from). + module.declare_function("js_array_indexOf_jsvalue", I32, &[I64, DOUBLE, DOUBLE, I32]); module.declare_function( "js_array_last_index_of_jsvalue", I32, &[I64, DOUBLE, DOUBLE, I32], ); module.declare_function("js_array_includes_f64", I32, &[I64, DOUBLE]); - module.declare_function("js_array_includes_jsvalue", I32, &[I64, DOUBLE]); + module.declare_function( + "js_array_includes_jsvalue", + I32, + &[I64, DOUBLE, DOUBLE, I32], + ); module.declare_function("js_map_size", I32, &[I64]); module.declare_function("js_map_clear", VOID, &[I64]); module.declare_function("js_set_clear", VOID, &[I64]); diff --git a/crates/perry-hir/src/analysis.rs b/crates/perry-hir/src/analysis.rs index e6e39f75d4..1e2ae43bf4 100644 --- a/crates/perry-hir/src/analysis.rs +++ b/crates/perry-hir/src/analysis.rs @@ -482,7 +482,7 @@ pub(crate) fn collect_assigned_locals_expr(expr: &Expr, assigned: &mut Vec { // These modify the array but don't reallocate } - Expr::ArrayIndexOf { array, value } | Expr::ArrayIncludes { array, value } => { + Expr::ArrayIndexOf { array, value, .. } | Expr::ArrayIncludes { array, value, .. } => { collect_assigned_locals_expr(array, assigned); collect_assigned_locals_expr(value, assigned); } diff --git a/crates/perry-hir/src/ir/expr.rs b/crates/perry-hir/src/ir/expr.rs index 442c618624..c2ce0ac90e 100644 --- a/crates/perry-hir/src/ir/expr.rs +++ b/crates/perry-hir/src/ir/expr.rs @@ -1231,7 +1231,8 @@ pub enum Expr { ArrayIndexOf { array: Box, value: Box, - }, // arr.indexOf(value) -> index + from_index: Option>, + }, // arr.indexOf(value, fromIndex?) -> index ArrayLastIndexOf { array: Box, value: Box, @@ -1240,7 +1241,8 @@ pub enum Expr { ArrayIncludes { array: Box, value: Box, - }, // arr.includes(value) -> boolean + from_index: Option>, + }, // arr.includes(value, fromIndex?) -> boolean ArraySlice { array: Box, start: Box, diff --git a/crates/perry-hir/src/js_transform/imports.rs b/crates/perry-hir/src/js_transform/imports.rs index 070e056b41..bb810fbebd 100644 --- a/crates/perry-hir/src/js_transform/imports.rs +++ b/crates/perry-hir/src/js_transform/imports.rs @@ -1002,9 +1002,21 @@ pub fn transform_expr( Expr::ArrayPush { value, .. } | Expr::ArrayUnshift { value, .. } | Expr::ArrayPushSpread { source: value, .. } => { transform_expr(value, js_imports, extern_func_to_js, local_name_to_js, tracker); } - Expr::ArrayIndexOf { array, value } | Expr::ArrayIncludes { array, value } => { + Expr::ArrayIndexOf { + array, + value, + from_index, + } + | Expr::ArrayIncludes { + array, + value, + from_index, + } => { transform_expr(array, js_imports, extern_func_to_js, local_name_to_js, tracker); transform_expr(value, js_imports, extern_func_to_js, local_name_to_js, tracker); + if let Some(fi) = from_index { + transform_expr(fi, js_imports, extern_func_to_js, local_name_to_js, tracker); + } } Expr::ArraySlice { array, start, end } => { transform_expr(array, js_imports, extern_func_to_js, local_name_to_js, tracker); diff --git a/crates/perry-hir/src/lower/expr_call/array_only_methods.rs b/crates/perry-hir/src/lower/expr_call/array_only_methods.rs index 6963a7bf1f..cfda221eff 100644 --- a/crates/perry-hir/src/lower/expr_call/array_only_methods.rs +++ b/crates/perry-hir/src/lower/expr_call/array_only_methods.rs @@ -618,7 +618,7 @@ pub(super) fn try_array_only_methods( // Fall through to generic dispatch — could be a user // object's `.join` method (drizzle's sql.join, etc.). } - "indexOf" if !args.is_empty() => { + "indexOf" if args.len() == 1 || args.len() == 2 => { let array_expr = lower_expr(ctx, &member.obj)?; if matches!( &array_expr, @@ -633,14 +633,17 @@ pub(super) fn try_array_only_methods( | Expr::ObjectValues(_) | Expr::PropertyGet { .. } ) { - let value_expr = args.into_iter().next().unwrap(); + let mut it = args.into_iter(); + let value_expr = it.next().unwrap(); + let from_index = it.next().map(Box::new); return Ok(Ok(Expr::ArrayIndexOf { array: Box::new(array_expr), value: Box::new(value_expr), + from_index, })); } } - "includes" if !args.is_empty() => { + "includes" if args.len() == 1 || args.len() == 2 => { let array_expr = lower_expr(ctx, &member.obj)?; // Don't treat error string properties as arrays let is_error_string_prop = matches!(&array_expr, @@ -662,10 +665,13 @@ pub(super) fn try_array_only_methods( | Expr::PropertyGet { .. } ) { - let value_expr = args.into_iter().next().unwrap(); + let mut it = args.into_iter(); + let value_expr = it.next().unwrap(); + let from_index = it.next().map(Box::new); return Ok(Ok(Expr::ArrayIncludes { array: Box::new(array_expr), value: Box::new(value_expr), + from_index, })); } } diff --git a/crates/perry-hir/src/lower/expr_call/imported_array_methods.rs b/crates/perry-hir/src/lower/expr_call/imported_array_methods.rs index 026556beda..0e2d0451c2 100644 --- a/crates/perry-hir/src/lower/expr_call/imported_array_methods.rs +++ b/crates/perry-hir/src/lower/expr_call/imported_array_methods.rs @@ -127,18 +127,27 @@ pub(super) fn try_imported_array_methods( } } "indexOf" => { + // #2804: carry the optional fromIndex (2nd arg). if !args.is_empty() { + let mut it = args.into_iter(); + let value = it.next().unwrap(); + let from_index = it.next().map(Box::new); return Ok(Ok(Expr::ArrayIndexOf { array: Box::new(extern_ref), - value: Box::new(args.into_iter().next().unwrap()), + value: Box::new(value), + from_index, })); } } "includes" => { if !args.is_empty() { + let mut it = args.into_iter(); + let value = it.next().unwrap(); + let from_index = it.next().map(Box::new); return Ok(Ok(Expr::ArrayIncludes { array: Box::new(extern_ref), - value: Box::new(args.into_iter().next().unwrap()), + value: Box::new(value), + from_index, })); } } diff --git a/crates/perry-hir/src/lower/expr_call/inline_array_methods.rs b/crates/perry-hir/src/lower/expr_call/inline_array_methods.rs index e985e53c79..6582aa4bc6 100644 --- a/crates/perry-hir/src/lower/expr_call/inline_array_methods.rs +++ b/crates/perry-hir/src/lower/expr_call/inline_array_methods.rs @@ -85,18 +85,27 @@ pub(super) fn try_inline_array_methods( } } "indexOf" => { + // #2804: carry the optional fromIndex (2nd arg). if !args.is_empty() { + let mut it = args.into_iter(); + let value = it.next().unwrap(); + let from_index = it.next().map(Box::new); return Ok(Ok(Expr::ArrayIndexOf { array: Box::new(array_expr), - value: Box::new(args.into_iter().next().unwrap()), + value: Box::new(value), + from_index, })); } } "includes" => { if !args.is_empty() { + let mut it = args.into_iter(); + let value = it.next().unwrap(); + let from_index = it.next().map(Box::new); return Ok(Ok(Expr::ArrayIncludes { array: Box::new(array_expr), - value: Box::new(args.into_iter().next().unwrap()), + value: Box::new(value), + from_index, })); } } diff --git a/crates/perry-hir/src/lower/expr_call/local_array_methods.rs b/crates/perry-hir/src/lower/expr_call/local_array_methods.rs index 6049789b77..2662c6c486 100644 --- a/crates/perry-hir/src/lower/expr_call/local_array_methods.rs +++ b/crates/perry-hir/src/lower/expr_call/local_array_methods.rs @@ -204,18 +204,27 @@ pub(super) fn try_local_array_methods( } } "indexOf" => { + // #2804: carry the optional fromIndex (2nd arg). if !args.is_empty() { + let mut it = args.into_iter(); + let value = it.next().unwrap(); + let from_index = it.next().map(Box::new); return Ok(Ok(Expr::ArrayIndexOf { array: Box::new(Expr::LocalGet(array_id)), - value: Box::new(args.into_iter().next().unwrap()), + value: Box::new(value), + from_index, })); } } "includes" => { if !args.is_empty() { + let mut it = args.into_iter(); + let value = it.next().unwrap(); + let from_index = it.next().map(Box::new); return Ok(Ok(Expr::ArrayIncludes { array: Box::new(Expr::LocalGet(array_id)), - value: Box::new(args.into_iter().next().unwrap()), + value: Box::new(value), + from_index, })); } } diff --git a/crates/perry-hir/src/monomorph/driver.rs b/crates/perry-hir/src/monomorph/driver.rs index 0c33cd9912..a05292aabc 100644 --- a/crates/perry-hir/src/monomorph/driver.rs +++ b/crates/perry-hir/src/monomorph/driver.rs @@ -436,9 +436,21 @@ fn collect_instantiations_in_expr( | Expr::ArrayPushSpread { source: value, .. } => { collect_instantiations_in_expr(value, ctx, module, idx); } - Expr::ArrayIndexOf { array, value } | Expr::ArrayIncludes { array, value } => { + Expr::ArrayIndexOf { + array, + value, + from_index, + } + | Expr::ArrayIncludes { + array, + value, + from_index, + } => { collect_instantiations_in_expr(array, ctx, module, idx); collect_instantiations_in_expr(value, ctx, module, idx); + if let Some(fi) = from_index { + collect_instantiations_in_expr(fi, ctx, module, idx); + } } Expr::ArraySlice { array, start, end } => { collect_instantiations_in_expr(array, ctx, module, idx); diff --git a/crates/perry-hir/src/monomorph/substitute_expr.rs b/crates/perry-hir/src/monomorph/substitute_expr.rs index 522794fa4f..2bc60ee032 100644 --- a/crates/perry-hir/src/monomorph/substitute_expr.rs +++ b/crates/perry-hir/src/monomorph/substitute_expr.rs @@ -438,9 +438,16 @@ pub(crate) fn substitute_expr(expr: &Expr, substitutions: &HashMap array_id: *array_id, value: Box::new(substitute_expr(value, substitutions)), }, - Expr::ArrayIndexOf { array, value } => Expr::ArrayIndexOf { + Expr::ArrayIndexOf { + array, + value, + from_index, + } => Expr::ArrayIndexOf { array: Box::new(substitute_expr(array, substitutions)), value: Box::new(substitute_expr(value, substitutions)), + from_index: from_index + .as_ref() + .map(|fi| Box::new(substitute_expr(fi, substitutions))), }, Expr::ArrayLastIndexOf { array, @@ -453,9 +460,16 @@ pub(crate) fn substitute_expr(expr: &Expr, substitutions: &HashMap .as_ref() .map(|fi| Box::new(substitute_expr(fi, substitutions))), }, - Expr::ArrayIncludes { array, value } => Expr::ArrayIncludes { + Expr::ArrayIncludes { + array, + value, + from_index, + } => Expr::ArrayIncludes { array: Box::new(substitute_expr(array, substitutions)), value: Box::new(substitute_expr(value, substitutions)), + from_index: from_index + .as_ref() + .map(|fi| Box::new(substitute_expr(fi, substitutions))), }, Expr::ArraySlice { array, start, end } => Expr::ArraySlice { array: Box::new(substitute_expr(array, substitutions)), diff --git a/crates/perry-hir/src/monomorph/update_call_sites.rs b/crates/perry-hir/src/monomorph/update_call_sites.rs index dcbdcb5c72..eaeed7ab4c 100644 --- a/crates/perry-hir/src/monomorph/update_call_sites.rs +++ b/crates/perry-hir/src/monomorph/update_call_sites.rs @@ -368,9 +368,21 @@ fn update_call_sites_in_expr( | Expr::ArrayPushSpread { source: value, .. } => { update_call_sites_in_expr(value, ctx, lookup); } - Expr::ArrayIndexOf { array, value } | Expr::ArrayIncludes { array, value } => { + Expr::ArrayIndexOf { + array, + value, + from_index, + } + | Expr::ArrayIncludes { + array, + value, + from_index, + } => { update_call_sites_in_expr(array, ctx, lookup); update_call_sites_in_expr(value, ctx, lookup); + if let Some(fi) = from_index { + update_call_sites_in_expr(fi, ctx, lookup); + } } Expr::ArraySlice { array, start, end } => { update_call_sites_in_expr(array, ctx, lookup); diff --git a/crates/perry-hir/src/stable_hash/expr.rs b/crates/perry-hir/src/stable_hash/expr.rs index d49187d47d..f18dd44ed1 100644 --- a/crates/perry-hir/src/stable_hash/expr.rs +++ b/crates/perry-hir/src/stable_hash/expr.rs @@ -327,9 +327,9 @@ impl SH for Expr { Expr::ArrayPop(id) => { tag(h, 251); id.hash(h); } Expr::ArrayShift(id) => { tag(h, 252); id.hash(h); } Expr::ArrayUnshift { array_id, value } => { tag(h, 253); array_id.hash(h); value.as_ref().hash(h); } - Expr::ArrayIndexOf { array, value } => { tag(h, 254); array.as_ref().hash(h); value.as_ref().hash(h); } + Expr::ArrayIndexOf { array, value, from_index } => { tag(h, 254); array.as_ref().hash(h); value.as_ref().hash(h); if let Some(fi) = from_index { tag(h, 1); fi.as_ref().hash(h); } else { tag(h, 0); } } Expr::ArrayLastIndexOf { array, value, from_index } => { tag(h, 11244); array.as_ref().hash(h); value.as_ref().hash(h); if let Some(fi) = from_index { tag(h, 1); fi.as_ref().hash(h); } else { tag(h, 0); } } - Expr::ArrayIncludes { array, value } => { tag(h, 255); array.as_ref().hash(h); value.as_ref().hash(h); } + Expr::ArrayIncludes { array, value, from_index } => { tag(h, 255); array.as_ref().hash(h); value.as_ref().hash(h); if let Some(fi) = from_index { tag(h, 1); fi.as_ref().hash(h); } else { tag(h, 0); } } Expr::ArraySlice { array, start, end } => { tag(h, 256); array.as_ref().hash(h); start.as_ref().hash(h); end.hash(h); } Expr::ArraySplice { array_id, start, delete_count, items, } => { tag(h, 257); array_id.hash(h); start.as_ref().hash(h); delete_count.hash(h); items.hash(h); } Expr::ArrayForEach { array, callback } => { tag(h, 258); array.as_ref().hash(h); callback.as_ref().hash(h); } @@ -549,7 +549,7 @@ impl SH for Expr { Expr::ReflectConstruct { target, args } => { tag(h, 439); target.as_ref().hash(h); args.as_ref().hash(h); } Expr::ReflectDefineProperty { target, key, descriptor, } => { tag(h, 440); target.as_ref().hash(h); key.as_ref().hash(h); descriptor.as_ref().hash(h); } Expr::ReflectGetPrototypeOf(e) => { tag(h, 441); e.as_ref().hash(h); } - Expr::ReflectIsExtensible(e) => { tag(h, 12045); e.as_ref().hash(h); } + Expr::ReflectIsExtensible(e) => { tag(h, 12048); e.as_ref().hash(h); } Expr::ReflectPreventExtensions(e) => { tag(h, 12046); e.as_ref().hash(h); } Expr::ReflectDefineMetadata { key, value, target, property_key, } => { tag(h, 12023); key.as_ref().hash(h); value.as_ref().hash(h); target.as_ref().hash(h); property_key.hash(h); } Expr::ReflectGetMetadata { key, target, property_key, } => { tag(h, 12024); key.as_ref().hash(h); target.as_ref().hash(h); property_key.hash(h); } diff --git a/crates/perry-hir/src/walker/expr_mut.rs b/crates/perry-hir/src/walker/expr_mut.rs index 7200e3550c..f789a51393 100644 --- a/crates/perry-hir/src/walker/expr_mut.rs +++ b/crates/perry-hir/src/walker/expr_mut.rs @@ -1359,11 +1359,17 @@ where | Expr::SetAdd { value, .. } => { f(value); } - Expr::ArrayIndexOf { array, value } | Expr::ArrayIncludes { array, value } => { - f(array); - f(value); + Expr::ArrayIndexOf { + array, + value, + from_index, + } + | Expr::ArrayIncludes { + array, + value, + from_index, } - Expr::ArrayLastIndexOf { + | Expr::ArrayLastIndexOf { array, value, from_index, diff --git a/crates/perry-hir/src/walker/expr_ref.rs b/crates/perry-hir/src/walker/expr_ref.rs index 83e9a2d056..71e181b88d 100644 --- a/crates/perry-hir/src/walker/expr_ref.rs +++ b/crates/perry-hir/src/walker/expr_ref.rs @@ -1334,11 +1334,17 @@ where | Expr::SetAdd { value, .. } => { f(value); } - Expr::ArrayIndexOf { array, value } | Expr::ArrayIncludes { array, value } => { - f(array); - f(value); + Expr::ArrayIndexOf { + array, + value, + from_index, + } + | Expr::ArrayIncludes { + array, + value, + from_index, } - Expr::ArrayLastIndexOf { + | Expr::ArrayLastIndexOf { array, value, from_index, diff --git a/crates/perry-runtime/src/array/search.rs b/crates/perry-runtime/src/array/search.rs index c440573abb..555684fde9 100644 --- a/crates/perry-runtime/src/array/search.rs +++ b/crates/perry-runtime/src/array/search.rs @@ -36,16 +36,55 @@ fn as_typed_array( } } -/// indexOf for arrays, using jsvalue comparison (handles NaN-boxed strings correctly) +/// Resolve a forward-search `fromIndex` (ECMA-262 ToIntegerOrInfinity + +/// clamping) into the first index to inspect. Returns `None` when nothing can +/// match (e.g. `fromIndex >= length`, including `+Infinity`). `has_from == 0` +/// means no argument was supplied → start at 0. `NaN` coerces to 0; negative +/// values count from the end and clamp at 0. +#[inline] +fn forward_start_index(length: i64, from_index: f64, has_from: i32) -> Option { + if has_from == 0 { + return Some(0); + } + let n = if from_index.is_nan() { + 0.0 + } else { + from_index.trunc() + }; + if n >= length as f64 { + // Covers +Infinity and any fromIndex past the end. + None + } else if n >= 0.0 { + Some(n as i64) + } else if n >= -(length as f64) { + Some(length + n as i64) + } else { + // fromIndex <= -length (including -Infinity): clamp to 0. + Some(0) + } +} + +/// indexOf for arrays, using jsvalue comparison (handles NaN-boxed strings +/// correctly). `from_index` / `has_from` implement the optional ECMA-262 +/// `fromIndex` argument (#2804); `has_from == 0` searches from index 0. #[no_mangle] -pub extern "C" fn js_array_indexOf_jsvalue(arr: *const ArrayHeader, value: f64) -> i32 { +pub extern "C" fn js_array_indexOf_jsvalue( + arr: *const ArrayHeader, + value: f64, + from_index: f64, + has_from: i32, +) -> i32 { let arr = clean_arr_ptr(arr); if arr.is_null() { return -1; } // TypedArray: strict-equality numeric search over the typed store. if let Some((ta, len)) = as_typed_array(arr) { - for i in 0..len { + let start = match forward_start_index(len as i64, from_index, has_from) { + Some(s) => s as i32, + None => return -1, + }; + for i in start..len { if crate::typedarray::js_typed_array_get(ta, i) == value { return i; } @@ -53,10 +92,14 @@ pub extern "C" fn js_array_indexOf_jsvalue(arr: *const ArrayHeader, value: f64) return -1; } unsafe { - let length = (*arr).length; + let length = (*arr).length as i64; + let start = match forward_start_index(length, from_index, has_from) { + Some(s) => s, + None => return -1, + }; let elements_ptr = (arr as *const u8).add(std::mem::size_of::()) as *const f64; - for i in 0..length as usize { - let element = *elements_ptr.add(i); + for i in start..length { + let element = *elements_ptr.add(i as usize); if crate::value::js_jsvalue_equals(element, value) == 1 { return i as i32; } @@ -170,9 +213,16 @@ pub extern "C" fn js_array_includes_f64(arr: *const ArrayHeader, value: f64) -> /// Check if an array includes a value using deep equality comparison. /// This handles NaN-boxed strings by comparing string contents. +/// `from_index` / `has_from` implement the optional ECMA-262 `fromIndex` +/// argument (#2804); `has_from == 0` searches from index 0. /// Returns 1 if found, 0 if not. #[no_mangle] -pub extern "C" fn js_array_includes_jsvalue(arr: *const ArrayHeader, value: f64) -> i32 { +pub extern "C" fn js_array_includes_jsvalue( + arr: *const ArrayHeader, + value: f64, + from_index: f64, + has_from: i32, +) -> i32 { let arr = clean_arr_ptr(arr); if arr.is_null() { return 0; @@ -180,7 +230,11 @@ pub extern "C" fn js_array_includes_jsvalue(arr: *const ArrayHeader, value: f64) // TypedArray: SameValueZero numeric search (so includes(NaN) is true for // float typed arrays). if let Some((ta, len)) = as_typed_array(arr) { - for i in 0..len { + let start = match forward_start_index(len as i64, from_index, has_from) { + Some(s) => s as i32, + None => return 0, + }; + for i in start..len { let e = crate::typedarray::js_typed_array_get(ta, i); if crate::value::js_jsvalue_same_value_zero(e, value) == 1 { return 1; @@ -189,15 +243,19 @@ pub extern "C" fn js_array_includes_jsvalue(arr: *const ArrayHeader, value: f64) return 0; } unsafe { - let length = (*arr).length; + let length = (*arr).length as i64; + let start = match forward_start_index(length, from_index, has_from) { + Some(s) => s, + None => return 0, + }; let elements_ptr = (arr as *const u8).add(std::mem::size_of::()) as *const f64; // `Array.prototype.includes` uses SameValueZero (ECMA-262 §23.1.3.16), // which differs from === in one place: NaN equals NaN. Routing // through `js_jsvalue_same_value_zero` preserves the `indexOf(NaN) === // -1` / `includes(NaN) === true` split. - for i in 0..length as usize { - let element = *elements_ptr.add(i); + for i in start..length { + let element = *elements_ptr.add(i as usize); if crate::value::js_jsvalue_same_value_zero(element, value) == 1 { return 1; } @@ -225,13 +283,20 @@ mod typed_search_tests { js_typed_array_set(ta, i as i32, *v); } let arr = ta as *const ArrayHeader; - assert_eq!(js_array_indexOf_jsvalue(arr, 2.0), 1); - assert_eq!(js_array_indexOf_jsvalue(arr, 3.0), 2); - assert_eq!(js_array_indexOf_jsvalue(arr, 9.0), -1); - assert_eq!(js_array_includes_jsvalue(arr, 3.0), 1); - assert_eq!(js_array_includes_jsvalue(arr, 9.0), 0); + assert_eq!(js_array_indexOf_jsvalue(arr, 2.0, 0.0, 0), 1); + assert_eq!(js_array_indexOf_jsvalue(arr, 3.0, 0.0, 0), 2); + assert_eq!(js_array_indexOf_jsvalue(arr, 9.0, 0.0, 0), -1); + assert_eq!(js_array_includes_jsvalue(arr, 3.0, 0.0, 0), 1); + assert_eq!(js_array_includes_jsvalue(arr, 9.0, 0.0, 0), 0); // indexOf uses strict equality → NaN never matches. - assert_eq!(js_array_indexOf_jsvalue(arr, f64::NAN), -1); + assert_eq!(js_array_indexOf_jsvalue(arr, f64::NAN, 0.0, 0), -1); + // fromIndex on a TypedArray: search from index 2 onward. + assert_eq!(js_array_indexOf_jsvalue(arr, 1.0, 2.0, 1), 4); + // negative fromIndex counts from end: -2 → index 3. + assert_eq!(js_array_indexOf_jsvalue(arr, 2.0, -2.0, 1), 3); + // +Infinity fromIndex never matches forward search. + assert_eq!(js_array_indexOf_jsvalue(arr, 1.0, f64::INFINITY, 1), -1); + assert_eq!(js_array_includes_jsvalue(arr, 1.0, f64::INFINITY, 1), 0); } /// SameValueZero: `includes(NaN)` is true for a Float64Array holding NaN, @@ -243,9 +308,9 @@ mod typed_search_tests { js_typed_array_set(ta, 1, f64::NAN); js_typed_array_set(ta, 2, 2.5); let arr = ta as *const ArrayHeader; - assert_eq!(js_array_includes_jsvalue(arr, f64::NAN), 1); - assert_eq!(js_array_indexOf_jsvalue(arr, f64::NAN), -1); - assert_eq!(js_array_indexOf_jsvalue(arr, 2.5), 2); + assert_eq!(js_array_includes_jsvalue(arr, f64::NAN, 0.0, 0), 1); + assert_eq!(js_array_indexOf_jsvalue(arr, f64::NAN, 0.0, 0), -1); + assert_eq!(js_array_indexOf_jsvalue(arr, 2.5, 0.0, 0), 2); } /// `lastIndexOf` on a registered TypedArray scans the typed backing store diff --git a/crates/perry-runtime/src/object/native_call_method.rs b/crates/perry-runtime/src/object/native_call_method.rs index de8addd3da..7914e1ab91 100644 --- a/crates/perry-runtime/src/object/native_call_method.rs +++ b/crates/perry-runtime/src/object/native_call_method.rs @@ -1492,8 +1492,16 @@ pub unsafe extern "C" fn js_native_call_method( return crate::array::js_array_reduce_right(arr, cb_ptr, has_init, init); } "flat" => { + // #2800: honor the optional depth argument. Omitted → + // depth 1 (legacy `js_array_flat`); supplied → route to + // the depth-aware helper, which applies JS number + // coercion (NaN/≤0 → 0, +Infinity → fully flat). let arr = raw_ptr as *const crate::array::ArrayHeader; - let result = crate::array::js_array_flat(arr); + let result = if args_len >= 1 && !args_ptr.is_null() { + crate::array::js_array_flat_depth(arr, *args_ptr) + } else { + crate::array::js_array_flat(arr) + }; return f64::from_bits(JSValue::pointer(result as *mut u8).bits()); } "flatMap" if args_len >= 1 && !args_ptr.is_null() => { @@ -1511,14 +1519,30 @@ pub unsafe extern "C" fn js_native_call_method( return f64::from_bits(JSValue::pointer(result as *mut u8).bits()); } "indexOf" if args_len >= 1 && !args_ptr.is_null() => { + // #2804: honor the optional fromIndex (2nd arg). let arr = raw_ptr as *const crate::array::ArrayHeader; let value = *args_ptr; - return crate::array::js_array_indexOf_jsvalue(arr, value) as f64; + let (from_index, has_from) = if args_len >= 2 { + (*args_ptr.add(1), 1) + } else { + (0.0, 0) + }; + return crate::array::js_array_indexOf_jsvalue( + arr, value, from_index, has_from, + ) as f64; } "includes" if args_len >= 1 && !args_ptr.is_null() => { + // #2804: honor the optional fromIndex (2nd arg). let arr = raw_ptr as *const crate::array::ArrayHeader; let value = *args_ptr; - let r = crate::array::js_array_includes_jsvalue(arr, value); + let (from_index, has_from) = if args_len >= 2 { + (*args_ptr.add(1), 1) + } else { + (0.0, 0) + }; + let r = crate::array::js_array_includes_jsvalue( + arr, value, from_index, has_from, + ); return f64::from_bits(JSValue::bool(r != 0).bits()); } "lastIndexOf" if args_len >= 1 && !args_ptr.is_null() => { @@ -1539,8 +1563,40 @@ pub unsafe extern "C" fn js_native_call_method( return crate::array::js_array_at(arr, *args_ptr); } "fill" if args_len >= 1 && !args_ptr.is_null() => { + // #2801: honor the optional start/end range. One arg → + // whole-array fill; 2+ args → range fill with the + // supplied start and (defaulting to +Infinity → + // clamps to length) end, mirroring the static path. let arr = raw_ptr as *mut crate::array::ArrayHeader; - let result = crate::array::js_array_fill(arr, *args_ptr); + let value = *args_ptr; + let result = if args_len >= 2 { + let start = *args_ptr.add(1); + let end = if args_len >= 3 { + *args_ptr.add(2) + } else { + f64::INFINITY + }; + crate::array::js_array_fill_range(arr, value, start, end) + } else { + crate::array::js_array_fill(arr, value) + }; + return f64::from_bits(JSValue::pointer(result as *mut u8).bits()); + } + "copyWithin" if args_len >= 1 && !args_ptr.is_null() => { + // #2802: dynamic dispatch for Array.prototype.copyWithin. + // Mirrors the static codegen path: require `target`, + // default omitted `start` to 0, pass has_end=0 when + // `end` is omitted. Mutates and returns the receiver. + let arr = raw_ptr as *mut crate::array::ArrayHeader; + let target = *args_ptr; + let start = if args_len >= 2 { *args_ptr.add(1) } else { 0.0 }; + let (has_end, end) = if args_len >= 3 { + (1, *args_ptr.add(2)) + } else { + (0, 0.0) + }; + let result = + crate::array::js_array_copy_within(arr, target, start, has_end, end); return f64::from_bits(JSValue::pointer(result as *mut u8).bits()); } "join" => { diff --git a/test-files/test_gap_array_optargs_2804_2801_2800_2802.ts b/test-files/test_gap_array_optargs_2804_2801_2800_2802.ts new file mode 100644 index 0000000000..4ecceae53c --- /dev/null +++ b/test-files/test_gap_array_optargs_2804_2801_2800_2802.ts @@ -0,0 +1,89 @@ +// Gap test for #2804 / #2801 / #2800 / #2802: +// Array prototype optional-argument semantics — indexOf/includes fromIndex, +// fill(value,start,end), flat(depth), copyWithin(target,start,end). +// Each method is exercised via BOTH a direct call and a dynamic-dispatch call +// (computed method name or `as any` receiver) so both codegen paths are tested. + +// ---------- #2804: indexOf / includes fromIndex ---------- +const arr = [1, 2, 1, NaN]; +const ta = new Float64Array([1, 2, 1, NaN]); + +// Direct calls. +console.log("indexOf from 1:", arr.indexOf(1, 1)); +console.log("indexOf from -2:", arr.indexOf(1, -2)); +console.log("indexOf from Infinity:", arr.indexOf(1, Infinity)); +console.log("indexOf default:", arr.indexOf(1)); +console.log("includes NaN:", arr.includes(NaN)); +console.log("includes from 4:", arr.includes(1, 4)); +console.log("includes from -2:", arr.includes(1, -2)); +console.log("includes from Infinity:", arr.includes(1, Infinity)); + +console.log("ta indexOf from 1:", ta.indexOf(1, 1)); +console.log("ta indexOf from -2:", ta.indexOf(1, -2)); +console.log("ta indexOf from Infinity:", ta.indexOf(1, Infinity)); +console.log("ta includes NaN:", ta.includes(NaN)); +console.log("ta includes from 4:", ta.includes(1, 4)); + +// Dynamic dispatch (computed method name). +const mi = "indexOf"; +const mc = "includes"; +console.log("dyn indexOf from 1:", (arr as any)[mi](1, 1)); +console.log("dyn indexOf from -2:", (arr as any)[mi](1, -2)); +console.log("dyn indexOf from Infinity:", (arr as any)[mi](1, Infinity)); +console.log("dyn includes from 4:", (arr as any)[mc](1, 4)); +console.log("dyn includes from -2:", (arr as any)[mc](1, -2)); +console.log("dyn includes NaN:", (arr as any)[mc](NaN)); + +// ---------- #2801: fill(value, start, end) ---------- +console.log("fill value only:", JSON.stringify([1, 2, 3, 4].fill(9))); +console.log("fill start:", JSON.stringify([1, 2, 3, 4].fill(9, 2))); +console.log("fill start end:", JSON.stringify([1, 2, 3, 4].fill(9, 1, 3))); +console.log("fill neg:", JSON.stringify([1, 2, 3, 4].fill(9, -3, -1))); + +const f1 = [1, 2, 3, 4]; +const f2 = [1, 2, 3, 4]; +const f3 = [1, 2, 3, 4]; +console.log("dyn fill start:", JSON.stringify((f1 as any).fill(9, 2))); +console.log("dyn fill start end:", JSON.stringify((f2 as any).fill(9, 1, 3))); +console.log("dyn fill neg:", JSON.stringify((f3 as any).fill(9, -3, -1))); + +// ---------- #2800: flat(depth) ---------- +function nest(depth: number): any { + let x: any = "leaf"; + for (let i = 0; i < depth; i++) x = [x]; + return x; +} +function residualDepth(out: any): number { + let d = 0; + let cur = out; + while (Array.isArray(cur)) { + d++; + cur = cur[0]; + if (d > 50) break; + } + return d; +} +for (const depth of [0, 1, 2, 32, 40, Infinity, NaN, -1]) { + console.log(`flat ${String(depth)}:`, residualDepth(nest(40).flat(depth))); +} +// Dynamic dispatch flat. +const fm = "flat"; +console.log("dyn flat 0:", residualDepth((nest(40) as any)[fm](0))); +console.log("dyn flat 2:", residualDepth((nest(40) as any)[fm](2))); +console.log("dyn flat Infinity:", residualDepth((nest(40) as any)[fm](Infinity))); +console.log("dyn flat default:", residualDepth((nest(40) as any)[fm]())); + +// ---------- #2802: copyWithin(target, start, end) ---------- +console.log("copyWithin target:", JSON.stringify([1, 2, 3, 4].copyWithin(1))); +console.log("copyWithin t start:", JSON.stringify([1, 2, 3, 4].copyWithin(1, 2))); +console.log("copyWithin t s e:", JSON.stringify([1, 2, 3, 4].copyWithin(1, 0, 2))); +console.log("copyWithin neg:", JSON.stringify([1, 2, 3, 4].copyWithin(-2, 0, -1))); + +const c1 = [1, 2, 3, 4]; +const c2 = [1, 2, 3, 4]; +const c3 = [1, 2, 3, 4]; +const c4 = [1, 2, 3, 4]; +console.log("dyn copyWithin target:", JSON.stringify((c1 as any).copyWithin(1))); +console.log("dyn copyWithin t start:", JSON.stringify((c2 as any).copyWithin(1, 2))); +console.log("dyn copyWithin t s e:", JSON.stringify((c3 as any).copyWithin(1, 0, 2))); +console.log("dyn copyWithin neg:", JSON.stringify((c4 as any).copyWithin(-2, 0, -1)));