@@ -22,188 +22,234 @@ public bool TryEmitMethodCall(IEmitterContext emitter, Expr receiver, string met
2222 emitter . EmitBoxIfNeeded ( receiver ) ;
2323
2424 // Handle both List<object> and $Array types
25- // For $Array, extract the Elements property
26- EmitGetListFromArrayOrList ( il , ctx ) ;
25+ // For $Array, extract the Elements property.
26+ // The returned local holds the ORIGINAL receiver, used below for
27+ // identity-preserving methods (sort/reverse/fill/copyWithin) and to
28+ // wrap "new array" method results back into $Array so downstream
29+ // code sees a $Array whenever the input was one.
30+ var receiverLocal = EmitGetListFromArrayOrList ( il , ctx ) ;
31+
32+ // Methods whose spec says "return this" — the caller expects the same
33+ // reference the receiver started with. Since we unwrapped to a List,
34+ // the runtime helper returns the inner List, not the $Array wrapper;
35+ // to preserve `arr === arr.sort()` we stash the wrapper and push it
36+ // back at the end.
37+ bool returnsReceiver = methodName is "sort" or "reverse" or "fill" or "copyWithin" ;
38+ // Methods whose spec says "return a new Array" — we want callers to
39+ // continue seeing a $Array after them (not a bare List<object?>), so
40+ // downstream array methods / runtime dispatch still match.
41+ bool returnsNewArray = methodName is
42+ "slice" or "concat" or "map" or "filter" or "flat" or "flatMap"
43+ or "splice" or "toReversed" or "toSorted" or "toSpliced" or "with" ;
2744
2845 switch ( methodName )
2946 {
3047 case "pop" :
3148 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayPop ) ;
32- return true ;
49+ break ;
3350
3451 case "shift" :
3552 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayShift ) ;
36- return true ;
53+ break ;
3754
3855 case "unshift" :
3956 // JS `arr.unshift(a, b, c)` prepends all args in order: [a,b,c,...orig].
4057 // ArrayUnshift(list, el) inserts one element at the start, so iterate
4158 // in reverse to preserve the final order.
4259 EmitVariadicListMutation ( emitter , arguments , ctx . Runtime ! . ArrayUnshift , reverse : true ) ;
43- return true ;
60+ break ;
4461
4562 case "push" :
4663 // JS `arr.push(a, b, c)` appends all args. Iterate forward.
4764 EmitVariadicListMutation ( emitter , arguments , ctx . Runtime ! . ArrayPush , reverse : false ) ;
48- return true ;
65+ break ;
4966
5067 case "slice" :
5168 EmitArgsArray ( emitter , arguments ) ;
5269 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArraySlice ) ;
53- return true ;
70+ break ;
5471
5572 case "map" :
5673 EmitSingleArgOrNull ( emitter , arguments ) ;
5774 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayMap ) ;
58- return true ;
75+ break ;
5976
6077 case "filter" :
6178 EmitSingleArgOrNull ( emitter , arguments ) ;
6279 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayFilter ) ;
63- return true ;
80+ break ;
6481
6582 case "forEach" :
6683 EmitSingleArgOrNull ( emitter , arguments ) ;
6784 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayForEach ) ;
6885 il . Emit ( OpCodes . Ldnull ) ; // forEach returns undefined
69- return true ;
86+ break ;
7087
7188 case "find" :
7289 EmitSingleArgOrNull ( emitter , arguments ) ;
7390 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayFind ) ;
74- return true ;
91+ break ;
7592
7693 case "findIndex" :
7794 EmitSingleArgOrNull ( emitter , arguments ) ;
7895 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayFindIndex ) ;
7996 il . Emit ( OpCodes . Box , ctx . Types . Double ) ;
80- return true ;
97+ break ;
8198
8299 case "some" :
83100 EmitSingleArgOrNull ( emitter , arguments ) ;
84101 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArraySome ) ;
85- return true ;
102+ break ;
86103
87104 case "every" :
88105 EmitSingleArgOrNull ( emitter , arguments ) ;
89106 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayEvery ) ;
90- return true ;
107+ break ;
91108
92109 case "reduce" :
93110 EmitArgsArray ( emitter , arguments ) ;
94111 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayReduce ) ;
95- return true ;
112+ break ;
96113
97114 case "reduceRight" :
98115 EmitArgsArray ( emitter , arguments ) ;
99116 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayReduceRight ) ;
100- return true ;
117+ break ;
101118
102119 case "join" :
103120 EmitSingleArgOrNull ( emitter , arguments ) ;
104121 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayJoin ) ;
105- return true ;
122+ break ;
106123
107124 case "concat" :
108125 EmitSingleArgOrNull ( emitter , arguments ) ;
109126 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayConcat ) ;
110- return true ;
127+ break ;
111128
112129 case "reverse" :
113130 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayReverse ) ;
114- return true ;
131+ break ;
115132
116133 case "flat" :
117134 EmitSingleArgOrNull ( emitter , arguments ) ;
118135 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayFlat ) ;
119- return true ;
136+ break ;
120137
121138 case "flatMap" :
122139 EmitSingleArgOrNull ( emitter , arguments ) ;
123140 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayFlatMap ) ;
124- return true ;
141+ break ;
125142
126143 case "includes" :
127144 EmitSingleArgOrNull ( emitter , arguments ) ;
128145 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayIncludes ) ;
129- return true ;
146+ break ;
130147
131148 case "indexOf" :
132149 EmitSingleArgOrNull ( emitter , arguments ) ;
133150 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayIndexOf ) ;
134151 il . Emit ( OpCodes . Box , ctx . Types . Double ) ;
135- return true ;
152+ break ;
136153
137154 case "sort" :
138155 EmitSingleArgOrNull ( emitter , arguments ) ;
139156 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArraySort ) ;
140- return true ;
157+ break ;
141158
142159 case "toSorted" :
143160 EmitSingleArgOrNull ( emitter , arguments ) ;
144161 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayToSorted ) ;
145- return true ;
162+ break ;
146163
147164 case "splice" :
148165 EmitArgsArray ( emitter , arguments ) ;
149166 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArraySplice ) ;
150- return true ;
167+ break ;
151168
152169 case "toSpliced" :
153170 EmitArgsArray ( emitter , arguments ) ;
154171 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayToSpliced ) ;
155- return true ;
172+ break ;
156173
157174 case "findLast" :
158175 EmitSingleArgOrNull ( emitter , arguments ) ;
159176 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayFindLast ) ;
160- return true ;
177+ break ;
161178
162179 case "findLastIndex" :
163180 EmitSingleArgOrNull ( emitter , arguments ) ;
164181 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayFindLastIndex ) ;
165182 il . Emit ( OpCodes . Box , ctx . Types . Double ) ;
166- return true ;
183+ break ;
167184
168185 case "toReversed" :
169186 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayToReversed ) ;
170- return true ;
187+ break ;
171188
172189 case "with" :
173190 EmitArgsArray ( emitter , arguments ) ;
174191 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayWith ) ;
175- return true ;
192+ break ;
176193
177194 case "at" :
178195 EmitSingleArgOrNull ( emitter , arguments ) ;
179196 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayAt ) ;
180- return true ;
197+ break ;
181198
182199 case "fill" :
183200 EmitArgsArray ( emitter , arguments ) ;
184201 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayFill ) ;
185- return true ;
202+ break ;
186203
187204 case "copyWithin" :
188205 EmitArgsArray ( emitter , arguments ) ;
189206 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayCopyWithin ) ;
190- return true ;
207+ break ;
191208
192209 case "entries" :
193210 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayEntries ) ;
194- return true ;
211+ break ;
195212
196213 case "keys" :
197214 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayKeys ) ;
198- return true ;
215+ break ;
199216
200217 case "values" :
201218 il . Emit ( OpCodes . Call , ctx . Runtime ! . ArrayValues ) ;
202- return true ;
219+ break ;
203220
204221 default :
205222 return false ;
206223 }
224+
225+ EmitPostCallAdjust ( il , ctx , receiverLocal , returnsReceiver , returnsNewArray ) ;
226+ return true ;
227+ }
228+
229+ /// <summary>
230+ /// After a call that leaves a List<object?> on the stack, adjust the
231+ /// top-of-stack so downstream code sees the expected JS value:
232+ /// - For "return this" methods: pop the list, push the saved <c>$Array</c>
233+ /// receiver (or the bare list if receiver wasn't a <c>$Array</c>).
234+ /// - For "return new array" methods: wrap the list in a fresh <c>$Array</c>.
235+ /// Called from per-case branches in <see cref="TryEmitMethodCall"/>.
236+ /// </summary>
237+ private static void EmitPostCallAdjust ( ILGenerator il , CompilationContext ctx , LocalBuilder receiverLocal , bool returnsReceiver , bool returnsNewArray )
238+ {
239+ if ( returnsReceiver )
240+ {
241+ // Stack: [list (the mutated inner List<object?>)]
242+ // We want: the ORIGINAL receiver (the $Array wrapper if it was one).
243+ il . Emit ( OpCodes . Pop ) ;
244+ il . Emit ( OpCodes . Ldloc , receiverLocal ) ;
245+ return ;
246+ }
247+
248+ if ( returnsNewArray )
249+ {
250+ // Stack: [list] → want: [new $Array(list)]
251+ il . Emit ( OpCodes . Newobj , ctx . Runtime ! . TSArrayCtor ) ;
252+ }
207253 }
208254
209255 /// <summary>
@@ -265,7 +311,23 @@ public bool TryEmitPropertyGet(IEmitterContext emitter, Expr receiver, string pr
265311
266312 var fallbackLabelNH = il . DefineLabel ( ) ;
267313 var endLabelNH = il . DefineLabel ( ) ;
314+ // Stage E.2 M2/M3: $Array inherits List<object?>, so `isinst List<object?>`
315+ // below matches $Array instances — but base `Count` only sees the dense
316+ // prefix, missing any sparse tail. Check $Array first and use its
317+ // LongLength getter (int-clamped Length would truncate lengths past
318+ // int.MaxValue; M3 acceptance demands `a.length === 2147483649` works).
319+ var tsArrayCheckLabel = il . DefineLabel ( ) ;
320+ il . Emit ( OpCodes . Ldloc , objLocal ) ;
321+ il . Emit ( OpCodes . Isinst , ctx . Runtime ! . TSArrayType ) ;
322+ il . Emit ( OpCodes . Brfalse , tsArrayCheckLabel ) ;
323+ il . Emit ( OpCodes . Ldloc , objLocal ) ;
324+ il . Emit ( OpCodes . Castclass , ctx . Runtime ! . TSArrayType ) ;
325+ il . Emit ( OpCodes . Callvirt , ctx . Runtime ! . TSArrayLongLengthGetter ) ;
326+ il . Emit ( OpCodes . Conv_R8 ) ;
327+ il . Emit ( OpCodes . Box , ctx . Types . Double ) ;
328+ il . Emit ( OpCodes . Br , endLabelNH ) ;
268329
330+ il . MarkLabel ( tsArrayCheckLabel ) ;
269331 il . Emit ( OpCodes . Ldloc , objLocal ) ;
270332 il . Emit ( OpCodes . Isinst , listTypeNH ) ;
271333 il . Emit ( OpCodes . Brfalse , fallbackLabelNH ) ;
@@ -301,8 +363,11 @@ public bool TryEmitPropertySet(IEmitterContext emitter, Expr receiver, string pr
301363 /// <summary>
302364 /// Emits code to convert an array value (either List<object> or $Array) to List<object>.
303365 /// The value is expected to be on the stack; leaves List<object> on the stack.
366+ /// Returns the local that stashes the ORIGINAL receiver — callers emitting
367+ /// identity-preserving methods (sort/reverse/fill/copyWithin) use this to
368+ /// push the receiver back after the runtime helper returns.
304369 /// </summary>
305- private static void EmitGetListFromArrayOrList ( ILGenerator il , CompilationContext ctx )
370+ private static LocalBuilder EmitGetListFromArrayOrList ( ILGenerator il , CompilationContext ctx )
306371 {
307372 var objLocal = il . DeclareLocal ( ctx . Types . Object ) ;
308373 il . Emit ( OpCodes . Stloc , objLocal ) ;
@@ -384,6 +449,7 @@ private static void EmitGetListFromArrayOrList(ILGenerator il, CompilationContex
384449 il . Emit ( OpCodes . Castclass , ctx . Types . ListOfObject ) ;
385450
386451 il . MarkLabel ( endLabel ) ;
452+ return objLocal ;
387453 }
388454
389455 /// <summary>
0 commit comments