@@ -579,13 +579,98 @@ class JSArray<E> extends JavaScriptObject implements List<E>, JSIndexable<E> {
579
579
580
580
void sort ([int Function (E , E )? compare]) {
581
581
checkMutable ('sort' );
582
- Sort .sort (this , compare ?? _compareAny);
582
+ final len = length;
583
+ if (len < 2 ) return ;
584
+ compare ?? = _compareAny;
585
+ if (len == 2 ) {
586
+ final a = this [0 ];
587
+ final b = this [1 ];
588
+ if (compare (a, b) > 0 ) {
589
+ // Hand-coded because the compiler does not understand the array is
590
+ // mutable at this point.
591
+ JS ('' , '#[#] = #' , this , 0 , b); // this[0] = b;
592
+ JS ('' , '#[#] = #' , this , 1 , a); // this[1] = a;
593
+ }
594
+ return ;
595
+ }
596
+
597
+ // Use JavaScript's sort. This requires some pre- and post- processing.
598
+ //
599
+ // Arrays have three kinds of element that represent Dart `null`:
600
+ //
601
+ // - `null` values, which are passed to the compare function.
602
+ //
603
+ // - `undefined` values, which are not passed to the comparator and follow
604
+ // the sorted values in the resulting order.
605
+ //
606
+ // - Empty slots or holes in the array, which look like `undefined`, are
607
+ // also not passed to the comparator, and are moved to the end of the
608
+ // array to follow the `undefined` values.
609
+ //
610
+ // Example
611
+ //
612
+ // [null, /*empty*/, undefined, 'z', /*empty*/, 'a', undefined].sort()
613
+ //
614
+ // --->
615
+ //
616
+ // ['a', null, 'z', undefined, undefined, /*empty*/, /*empty*/]
617
+ //
618
+ // (null goes between 'a' and 'z' because JavaScript's default comparator
619
+ // works on `ToString` of the element).
620
+ //
621
+ // In order to have the undefined and empty elements behave as `null`, they
622
+ // are overwritten with `null` before sorting, and reinstated as an
623
+ // `undefined` value after. Since all the `null` values are
624
+ // indistinguishable, a count is sufficient.
625
+ //
626
+ // The reason we bother with reinstating `undefined` values is so that
627
+ // sorting does not change the contents of an array that has `undefined`
628
+ // values from js-interop. Empty slots are not preserved and become
629
+ // non-empty slots holding the `undefined` value (which would happen anyway
630
+ // with an assignment like `a[i] = a[j]`.
631
+
632
+ int undefineds = 0 ;
633
+ // The element type might exclude the possibility of there being `null`s,
634
+ // but only in sound null safety mode.
635
+ if (JS_GET_FLAG ('LEGACY' ) || null is E ) {
636
+ for (int i = 0 ; i < length; i++ ) {
637
+ final E element = JS ('' , '#[#]' , this , i);
638
+ if (JS ('' , '# === void 0' , element)) {
639
+ // Hand-coded write since `this[i] = null;` is a compile-time error
640
+ // due to `E` not being nullable.
641
+ JS ('' , '#[#] = #' , this , i, null );
642
+ undefineds++ ;
643
+ }
644
+ }
645
+ }
646
+ JS ('' , '#.sort(#)' , this , convertDartClosureToJS (compare, 2 ));
647
+
648
+ if (undefineds > 0 ) _replaceSomeNullsWithUndefined (undefineds);
583
649
}
584
650
585
651
static int _compareAny (a, b) {
586
652
return Comparable .compare (a, b);
587
653
}
588
654
655
+ // This is separate function since in many programs sorting an array with
656
+ // nulls or undefined values is rare. Keeping the code separate reduces
657
+ // potential JIT deoptimizations.
658
+ @pragma ('dart2js:never-inline' )
659
+ void _replaceSomeNullsWithUndefined (int count) {
660
+ assert (count > 0 );
661
+ int i = length;
662
+ // Scan backwards for `null`s and replace one-by-one. They are not
663
+ // necessarily adjacent if the compare function places Dart `null` in the
664
+ // same equivalence class as some non-null value.
665
+ while (i-- > 0 ) {
666
+ final E element = JS ('' , '#[#]' , this , i);
667
+ if (JS ('' , '# === null' , element)) {
668
+ JS ('' , '#[#] = void 0' , this , i);
669
+ if (-- count == 0 ) break ;
670
+ }
671
+ }
672
+ }
673
+
589
674
void shuffle ([Random ? random]) {
590
675
checkMutable ('shuffle' );
591
676
if (random == null ) random = new Random ();
0 commit comments