@@ -302,6 +302,8 @@ pub fn initialize_array<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'g
302302 "fill" ,
303303 "find" ,
304304 "findIndex" ,
305+ "findLast" ,
306+ "findLastIndex" ,
305307 "flat" ,
306308 "flatMap" ,
307309 "includes" ,
@@ -4351,64 +4353,67 @@ pub(crate) fn handle_array_instance_method<'gc>(
43514353 Ok ( Value :: String ( utf8_to_utf16 ( & result) ) )
43524354 }
43534355 "flat" => {
4354- let depth = if !args. is_empty ( ) {
4355- match args[ 0 ] . clone ( ) {
4356- Value :: Number ( n) => n as usize ,
4357- _ => 1 ,
4358- }
4356+ // §23.1.3.12 Array.prototype.flat ( [ depth ] )
4357+ // 1. Let O be ? ToObject(this value).
4358+ // 2. Let sourceLen be ? LengthOfArrayLike(O).
4359+ let source_len = length_of_array_like ( mc, env, object) ?;
4360+
4361+ // 3. Let depthNum be 1 (default).
4362+ // 4. If depth is not undefined, let depthNum be ? ToIntegerOrInfinity(depth).
4363+ let depth_num: f64 = if args. is_empty ( ) || matches ! ( args[ 0 ] , Value :: Undefined ) {
4364+ 1.0
43594365 } else {
4360- 1
4366+ to_integer_or_infinity_local ( mc , env , & args [ 0 ] ) ?
43614367 } ;
43624368
4363- let mut result = Vec :: new ( ) ;
4364- flatten_array ( mc, object, & mut result, depth) ?;
4365-
4369+ // 5. Let A be ? ArraySpeciesCreate(O, 0).
43664370 let new_array = array_species_create_impl ( mc, env, object, 0.0 ) ?;
4367- for ( i, val) in result. iter ( ) . enumerate ( ) {
4368- create_data_property_or_throw ( mc, & new_array, i, val) ?;
4371+
4372+ // 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum).
4373+ flatten_into_array_spec ( mc, env, & new_array, object, source_len, 0 , depth_num, None , None ) ?;
4374+
4375+ // 7. Return A.
4376+ // For true arrays, update length; for species objects, leave it alone.
4377+ if is_array ( mc, & new_array) {
4378+ let final_len = object_get_length ( & new_array) . unwrap_or ( 0 ) ;
4379+ set_array_length ( mc, & new_array, final_len) ?;
43694380 }
4370- set_array_length ( mc, & new_array, result. len ( ) ) ?;
43714381 Ok ( Value :: Object ( new_array) )
43724382 }
43734383 "flatMap" => {
4374- if args. is_empty ( ) {
4375- return Err ( raise_eval_error ! ( "Array.flatMap expects at least one argument" ) . into ( ) ) ;
4376- }
4377-
4378- let callback_val = args[ 0 ] . clone ( ) ;
4379- let current_len = get_array_length ( mc, object) . unwrap_or ( 0 ) ;
4384+ // §23.1.3.11 Array.prototype.flatMap ( mapperFunction [ , thisArg ] )
4385+ // 1. Let O be ? ToObject(this value).
4386+ // 2. Let sourceLen be ? LengthOfArrayLike(O).
4387+ let source_len = length_of_array_like ( mc, env, object) ?;
43804388
4381- let mut result = Vec :: new ( ) ;
4382- for i in 0 ..current_len {
4383- if let Some ( val) = object_get_key_value ( object, i) {
4384- // Support inline closures wrapped as objects with internal closure.
4385- let actual_func = if let Value :: Object ( obj) = & callback_val {
4386- if let Some ( prop) = obj. borrow ( ) . get_closure ( ) {
4387- prop. borrow ( ) . clone ( )
4388- } else {
4389- callback_val. clone ( )
4390- }
4391- } else {
4392- callback_val. clone ( )
4393- } ;
4394-
4395- let args = vec ! [ val. borrow( ) . clone( ) , Value :: Number ( i as f64 ) , Value :: Object ( * object) ] ;
4396-
4397- let mapped_val = match & actual_func {
4398- Value :: Closure ( cl) => crate :: core:: call_closure ( mc, cl, None , & args, env, None ) ?,
4399- Value :: Function ( name) => crate :: js_function:: handle_global_function ( mc, name, & args, env) ?,
4400- _ => return Err ( raise_eval_error ! ( "Array.flatMap expects a function" ) . into ( ) ) ,
4401- } ;
4402-
4403- flatten_single_value ( mc, & mapped_val, & mut result, 1 ) ?;
4404- }
4389+ // 3. If IsCallable(mapperFunction) is false, throw a TypeError exception.
4390+ let callback_val = args. first ( ) . cloned ( ) . unwrap_or ( Value :: Undefined ) ;
4391+ if !is_callable_val ( & callback_val) {
4392+ return Err ( raise_type_error ! ( "flatMap mapper is not a function" ) . into ( ) ) ;
44054393 }
44064394
4395+ // 4. Let A be ? ArraySpeciesCreate(O, 0).
44074396 let new_array = array_species_create_impl ( mc, env, object, 0.0 ) ?;
4408- for ( i, val) in result. iter ( ) . enumerate ( ) {
4409- create_data_property_or_throw ( mc, & new_array, i, val) ?;
4397+
4398+ // 5. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg).
4399+ let this_arg = args. get ( 1 ) . cloned ( ) . unwrap_or ( Value :: Undefined ) ;
4400+ flatten_into_array_spec (
4401+ mc,
4402+ env,
4403+ & new_array,
4404+ object,
4405+ source_len,
4406+ 0 ,
4407+ 1.0 ,
4408+ Some ( & callback_val) ,
4409+ Some ( & this_arg) ,
4410+ ) ?;
4411+
4412+ // 6. Return A.
4413+ if is_array ( mc, & new_array) {
4414+ let final_len = object_get_length ( & new_array) . unwrap_or ( 0 ) ;
4415+ set_array_length ( mc, & new_array, final_len) ?;
44104416 }
4411- set_array_length ( mc, & new_array, result. len ( ) ) ?;
44124417 Ok ( Value :: Object ( new_array) )
44134418 }
44144419 "copyWithin" => {
@@ -4630,16 +4635,8 @@ pub(crate) fn handle_array_instance_method<'gc>(
46304635 let callback_val = args. first ( ) . cloned ( ) . unwrap_or ( Value :: Undefined ) ;
46314636 let this_arg = args. get ( 1 ) . cloned ( ) . unwrap_or ( Value :: Undefined ) ;
46324637
4633- let len_val = crate :: core:: get_property_with_accessors ( mc, env, object, "length" ) ?;
4634- let len_prim = crate :: core:: to_primitive ( mc, & len_val, "number" , env) ?;
4635- let len_num = crate :: core:: to_number ( & len_prim) ?;
4636- let current_len = if len_num. is_nan ( ) || len_num <= 0.0 {
4637- 0usize
4638- } else if !len_num. is_finite ( ) {
4639- usize:: MAX
4640- } else {
4641- len_num. floor ( ) as usize
4642- } ;
4638+ // 2. Let len be ? LengthOfArrayLike(O).
4639+ let current_len = length_of_array_like ( mc, env, object) ?;
46434640
46444641 let callback_callable = match & callback_val {
46454642 Value :: Closure ( _)
@@ -4692,16 +4689,8 @@ pub(crate) fn handle_array_instance_method<'gc>(
46924689 let callback_val = args. first ( ) . cloned ( ) . unwrap_or ( Value :: Undefined ) ;
46934690 let this_arg = args. get ( 1 ) . cloned ( ) . unwrap_or ( Value :: Undefined ) ;
46944691
4695- let len_val = crate :: core:: get_property_with_accessors ( mc, env, object, "length" ) ?;
4696- let len_prim = crate :: core:: to_primitive ( mc, & len_val, "number" , env) ?;
4697- let len_num = crate :: core:: to_number ( & len_prim) ?;
4698- let current_len = if len_num. is_nan ( ) || len_num <= 0.0 {
4699- 0usize
4700- } else if !len_num. is_finite ( ) {
4701- usize:: MAX
4702- } else {
4703- len_num. floor ( ) as usize
4704- } ;
4692+ // 2. Let len be ? LengthOfArrayLike(O).
4693+ let current_len = length_of_array_like ( mc, env, object) ?;
47054694
47064695 let callback_callable = match & callback_val {
47074696 Value :: Closure ( _)
@@ -4748,48 +4737,156 @@ pub(crate) fn handle_array_instance_method<'gc>(
47484737}
47494738
47504739// Helper functions for array flattening
4751- fn flatten_array < ' gc > (
4740+ /// ToIntegerOrInfinity (local helper for array methods)
4741+ fn to_integer_or_infinity_local < ' gc > (
47524742 mc : & MutationContext < ' gc > ,
4753- object : & JSObjectDataPtr < ' gc > ,
4754- result : & mut Vec < Value < ' gc > > ,
4755- depth : usize ,
4756- ) -> Result < ( ) , JSError > {
4757- let current_len = get_array_length ( mc , object ) . unwrap_or ( 0 ) ;
4758-
4759- for i in 0 ..current_len {
4760- if let Some ( val ) = object_get_key_value ( object , i ) {
4761- flatten_single_value ( mc , & val . borrow ( ) , result , depth ) ? ;
4762- }
4743+ env : & JSObjectDataPtr < ' gc > ,
4744+ val : & Value < ' gc > ,
4745+ ) -> Result < f64 , EvalError < ' gc > > {
4746+ let n = crate :: core :: to_number_with_env ( mc , env , val ) ? ;
4747+ if n . is_nan ( ) || n == 0.0 {
4748+ Ok ( 0.0 )
4749+ } else if !n . is_finite ( ) {
4750+ Ok ( n )
4751+ } else {
4752+ Ok ( n . trunc ( ) )
47634753 }
4764- Ok ( ( ) )
47654754}
47664755
4767- fn flatten_single_value < ' gc > (
4756+ /// LengthOfArrayLike(obj) — spec 7.3.2
4757+ /// Returns ℝ(ToLength(Get(obj, "length"))).
4758+ fn length_of_array_like < ' gc > (
47684759 mc : & MutationContext < ' gc > ,
4769- value : & Value < ' gc > ,
4770- result : & mut Vec < Value < ' gc > > ,
4771- depth : usize ,
4772- ) -> Result < ( ) , JSError > {
4773- if depth == 0 {
4774- result. push ( value. clone ( ) ) ;
4775- return Ok ( ( ) ) ;
4760+ env : & JSObjectDataPtr < ' gc > ,
4761+ obj : & JSObjectDataPtr < ' gc > ,
4762+ ) -> Result < usize , EvalError < ' gc > > {
4763+ let len_val = crate :: core:: get_property_with_accessors ( mc, env, obj, "length" ) ?;
4764+ to_length ( mc, env, & len_val)
4765+ }
4766+
4767+ /// ToLength(argument) — spec 7.1.20
4768+ fn to_length < ' gc > ( mc : & MutationContext < ' gc > , env : & JSObjectDataPtr < ' gc > , val : & Value < ' gc > ) -> Result < usize , EvalError < ' gc > > {
4769+ let len = to_integer_or_infinity_local ( mc, env, val) ?;
4770+ if len <= 0.0 {
4771+ Ok ( 0 )
4772+ } else {
4773+ Ok ( ( len. min ( 9007199254740991.0 ) ) as usize )
47764774 }
4775+ }
47774776
4778- match value {
4777+ /// IsCallable check (mirrors spec § 7.2.3)
4778+ fn is_callable_val < ' gc > ( val : & Value < ' gc > ) -> bool {
4779+ match val {
4780+ Value :: Function ( _)
4781+ | Value :: Closure ( _)
4782+ | Value :: AsyncClosure ( _)
4783+ | Value :: GeneratorFunction ( ..)
4784+ | Value :: AsyncGeneratorFunction ( ..) => true ,
47794785 Value :: Object ( obj) => {
4780- // Check if it's an array-like object
4781- let is_arr = { is_array ( mc, obj) } ;
4782- if is_arr {
4783- flatten_array ( mc, obj, result, depth - 1 ) ?;
4784- } else {
4785- result. push ( Value :: Object ( * obj) ) ;
4786- }
4786+ obj. borrow ( ) . get_closure ( ) . is_some ( )
4787+ || obj. borrow ( ) . class_def . is_some ( )
4788+ || crate :: core:: slot_get ( obj, & InternalSlot :: NativeCtor ) . is_some ( )
4789+ || crate :: core:: slot_get ( obj, & InternalSlot :: BoundTarget ) . is_some ( )
4790+ || crate :: core:: slot_get ( obj, & InternalSlot :: Callable ) . is_some ( )
4791+ }
4792+ _ => false ,
4793+ }
4794+ }
4795+
4796+ /// HasProperty through proxy / TypedArray if needed
4797+ fn has_property_spec < ' gc > ( mc : & MutationContext < ' gc > , obj : & JSObjectDataPtr < ' gc > , key : & str ) -> Result < bool , EvalError < ' gc > > {
4798+ if let Some ( proxy_cell) = crate :: core:: slot_get ( obj, & InternalSlot :: Proxy )
4799+ && let Value :: Proxy ( proxy) = & * proxy_cell. borrow ( )
4800+ {
4801+ Ok ( crate :: js_proxy:: proxy_has_property ( mc, proxy, key. to_string ( ) ) ?)
4802+ } else if let Some ( ta_cell) = crate :: core:: slot_get ( obj, & InternalSlot :: TypedArray ) {
4803+ // TypedArray [[HasProperty]]: check if key is a valid integer index
4804+ if let Value :: TypedArray ( ta) = & * ta_cell. borrow ( )
4805+ && let Some ( idx_f) = crate :: js_typedarray:: canonical_numeric_index_string ( key)
4806+ {
4807+ return Ok ( crate :: js_typedarray:: is_valid_integer_index ( ta, idx_f) ) ;
4808+ }
4809+ // Non-numeric key: fall through to own properties
4810+ Ok ( object_get_key_value ( obj, key) . is_some ( ) )
4811+ } else {
4812+ Ok ( object_get_key_value ( obj, key) . is_some ( ) )
4813+ }
4814+ }
4815+
4816+ /// FlattenIntoArray — spec 23.1.3.11.1
4817+ /// target: the result array to populate
4818+ /// source: the current array being flattened
4819+ /// source_len: length of source
4820+ /// start: target start index
4821+ /// depth: remaining depth
4822+ /// mapper_function: optional mapper (for flatMap only)
4823+ /// this_arg: optional thisArg for mapper
4824+ /// Returns: the next target index after insertion
4825+ #[ allow( clippy:: too_many_arguments) ]
4826+ fn flatten_into_array_spec < ' gc > (
4827+ mc : & MutationContext < ' gc > ,
4828+ env : & JSObjectDataPtr < ' gc > ,
4829+ target : & JSObjectDataPtr < ' gc > ,
4830+ source : & JSObjectDataPtr < ' gc > ,
4831+ source_len : usize ,
4832+ start : usize ,
4833+ depth : f64 ,
4834+ mapper_function : Option < & Value < ' gc > > ,
4835+ this_arg : Option < & Value < ' gc > > ,
4836+ ) -> Result < usize , EvalError < ' gc > > {
4837+ let mut target_index = start;
4838+
4839+ for source_index in 0 ..source_len {
4840+ let p = source_index. to_string ( ) ;
4841+
4842+ // b. Let exists be ? HasProperty(source, P).
4843+ if !has_property_spec ( mc, source, & p) ? {
4844+ continue ;
47874845 }
4788- _ => {
4789- result. push ( value. clone ( ) ) ;
4846+
4847+ // c.i. Let element be ? Get(source, P).
4848+ let mut element = crate :: core:: get_property_with_accessors ( mc, env, source, & * p) ?;
4849+
4850+ // c.ii. If mapperFunction is present, let element be ? Call(mapperFunction, thisArg, «element, sourceIndex, source»).
4851+ if let Some ( mapper) = mapper_function {
4852+ let default_this = Value :: Undefined ;
4853+ let t = this_arg. unwrap_or ( & default_this) ;
4854+ let call_args = vec ! [ element, Value :: Number ( source_index as f64 ) , Value :: Object ( * source) ] ;
4855+ element = crate :: core:: evaluate_call_dispatch ( mc, env, mapper, Some ( t) , & call_args) ?;
4856+ }
4857+
4858+ // c.iii. Let shouldFlatten be false.
4859+ // c.iv. If depth > 0, let shouldFlatten be ? IsArray(element).
4860+ let should_flatten = if depth > 0.0 {
4861+ match & element {
4862+ Value :: Object ( obj) => is_array_spec ( mc, obj) ?,
4863+ _ => false ,
4864+ }
4865+ } else {
4866+ false
4867+ } ;
4868+
4869+ if should_flatten {
4870+ // c.v. If shouldFlatten is true:
4871+ // 1. If depth is +∞, let newDepth be +∞. Otherwise, let newDepth be depth - 1.
4872+ let new_depth = if depth == f64:: INFINITY { f64:: INFINITY } else { depth - 1.0 } ;
4873+ if let Value :: Object ( inner_obj) = & element {
4874+ let inner_len = length_of_array_like ( mc, env, inner_obj) ?;
4875+ target_index = flatten_into_array_spec ( mc, env, target, inner_obj, inner_len, target_index, new_depth, None , None ) ?;
4876+ }
4877+ } else {
4878+ // c.vi. Else:
4879+ // 1. If targetIndex ≥ 2^53 - 1, throw a TypeError.
4880+ if target_index >= 9007199254740991 {
4881+ return Err ( raise_type_error ! ( "FlattenIntoArray: target index exceeds safe integer limit" ) . into ( ) ) ;
4882+ }
4883+ // 2. Perform ? CreateDataPropertyOrThrow(target, targetIndex, element).
4884+ create_data_property_or_throw ( mc, target, target_index, & element) ?;
4885+ target_index += 1 ;
47904886 }
47914887 }
4792- Ok ( ( ) )
4888+
4889+ Ok ( target_index)
47934890}
47944891
47954892/// Check if an object is an Array
0 commit comments