@@ -18,6 +18,8 @@ enum SimpleString: String {
18
18
case emoji = " 😀😃🤢🤮👩🏿🎤🧛🏻♂️🧛🏻♂️👩👩👦👦 "
19
19
}
20
20
21
+ /// Print out a full list of indices in every view of `string`.
22
+ /// This is useful while debugging test failures in this test.
21
23
func dumpIndices( _ string: String ) {
22
24
print ( " ------------------------------------------------------------------- " )
23
25
print ( " String: \( String ( reflecting: string) ) " )
@@ -350,7 +352,7 @@ suite.test("Exhaustive Index Interchange")
350
352
return
351
353
}
352
354
353
- dumpIndices ( str)
355
+ // dumpIndices(str)
354
356
355
357
var curCharIdx = str. startIndex
356
358
var curScalarIdx = str. startIndex
@@ -481,10 +483,9 @@ suite.test("Exhaustive Index Interchange")
481
483
#endif
482
484
483
485
extension Collection {
484
- // Assuming both `self` and `other` are sorted, call `body` for each element
485
- // `a` in `other` together with the slice in `self` that starts with the first
486
- // element in `self` that is greater than or equal to `a`, up to the first
487
- // element that is greater than or equal to the next value in `other`.
486
+ // Assuming both `self` and `other` use the same index space, call `body` for
487
+ // each index `i` in `other`, along with the slice in `self` that begins at
488
+ // `i` and ends at the index following it in `other`.
488
489
//
489
490
// `other` must start with an item that is less than or equal to the first
490
491
// item in `self`.
@@ -516,9 +517,9 @@ extension Collection {
516
517
}
517
518
518
519
extension String {
519
- // Returns a list of every valid index in every string view, including end
520
- // indices. We keep equal indices originating from different views because
521
- // they may have different grapheme size caches or flags etc.
520
+ // Returns a list of every valid index in every string view, optionally
521
+ // including end indices. We keep equal indices originating from different
522
+ // views because they may have different grapheme size caches or flags etc.
522
523
func allIndices( includingEnd: Bool = true ) -> [ String . Index ] {
523
524
var r = Array ( self . indices)
524
525
if includingEnd { r. append ( self . endIndex) }
@@ -567,6 +568,23 @@ extension String {
567
568
}
568
569
}
569
570
571
+ extension Substring {
572
+ // Returns a list of every valid index in every string view, optionally
573
+ // including end indices. We keep equal indices originating from different
574
+ // views because they may have different grapheme size caches or flags etc.
575
+ func allIndices( includingEnd: Bool = true ) -> [ String . Index ] {
576
+ var r = Array ( self . indices)
577
+ if includingEnd { r. append ( self . endIndex) }
578
+ r += Array ( self . unicodeScalars. indices)
579
+ if includingEnd { r. append ( self . unicodeScalars. endIndex) }
580
+ r += Array ( self . utf8. indices)
581
+ if includingEnd { r. append ( self . utf8. endIndex) }
582
+ r += Array ( self . utf16. indices)
583
+ if includingEnd { r. append ( self . utf16. endIndex) }
584
+ return r
585
+ }
586
+ }
587
+
570
588
suite. test ( " Fully exhaustive index interchange " )
571
589
. forEach ( in: examples) { string in
572
590
guard #available( SwiftStdlib 5 . 7 , * ) else {
@@ -578,7 +596,7 @@ suite.test("Fully exhaustive index interchange")
578
596
return
579
597
}
580
598
581
- dumpIndices ( string)
599
+ // dumpIndices(string)
582
600
583
601
let scalarMap = string. scalarMap ( )
584
602
let characterMap = string. characterMap ( )
@@ -627,7 +645,9 @@ suite.test("Fully exhaustive index interchange")
627
645
// The substring `string[i..<j]` does not round its bounds to
628
646
// `Character` boundaries, so it may have a different count than what
629
647
// the base string reports.
630
- let s = String . UnicodeScalarView ( string. unicodeScalars [ i ..< j] )
648
+ let si = scalarMap [ i] !. index
649
+ let sj = scalarMap [ j] !. index
650
+ let s = String . UnicodeScalarView ( string. unicodeScalars [ si ..< sj] )
631
651
let substringDistance = String ( s) . count
632
652
633
653
let substring = string [ i ..< j]
@@ -650,6 +670,12 @@ suite.test("Fully exhaustive index interchange")
650
670
""" )
651
671
652
672
// Check reachability of substring bounds.
673
+ var i = substring. startIndex
674
+ for _ in 0 ..< substringDistance {
675
+ substring. formIndex ( after: & i)
676
+ }
677
+ expectEqual ( i, substring. endIndex)
678
+
653
679
expectEqual (
654
680
substring. index ( substring. startIndex, offsetBy: substringDistance) ,
655
681
substring. endIndex,
@@ -763,7 +789,6 @@ suite.test("Global vs local grapheme cluster boundaries") {
763
789
expectEqual ( str. index ( before: s [ 2 ] ) , s [ 0 ] ) // s[2] ≅ s[1]
764
790
expectEqual ( str. index ( before: s [ 1 ] ) , s [ 0 ] )
765
791
766
- dumpIndices ( str)
767
792
// UTF-8
768
793
expectEqual ( str. utf8. count, 18 )
769
794
expectEqual ( str. index ( after: u8 [ 0 ] ) , u8 [ 1 ] )
@@ -839,14 +864,14 @@ suite.test("Index encoding correction") {
839
864
// If the mutation's effect included the data addressed by the original index,
840
865
// then we may still get nonsensical results.
841
866
var s = ( " 🫱🏼🫲🏽 a 🧑🏽🌾 b " as NSString ) as String
842
- dumpIndices ( s)
867
+ // dumpIndices(s)
843
868
844
869
let originals = s. allIndices ( includingEnd: false ) . map {
845
870
( $0, s [ $0] , s. unicodeScalars [ $0] , s. utf8 [ $0] , s. utf16 [ $0] )
846
871
}
847
872
848
873
s. append ( " . " )
849
- dumpIndices ( s)
874
+ // dumpIndices(s)
850
875
851
876
for (i, char, scalar, u8, u16) in originals {
852
877
expectEqual ( s [ i] , char, " i: \( i) " )
@@ -856,3 +881,152 @@ suite.test("Index encoding correction") {
856
881
}
857
882
}
858
883
#endif
884
+
885
+ suite. test ( " String.replaceSubrange index validation " )
886
+ . forEach ( in: examples) { string in
887
+ guard #available( SwiftStdlib 5 . 7 , * ) else {
888
+ // Index navigation in 5.7 always rounds input indices down to the nearest
889
+ // Character, so that we always have a well-defined distance between
890
+ // indices, even if they aren't valid.
891
+ //
892
+ // 5.6 and below did not behave consistently in this case.
893
+ return
894
+ }
895
+
896
+ //dumpIndices(string)
897
+
898
+ let scalarMap = string. scalarMap ( )
899
+ let allIndices = string. allIndices ( )
900
+
901
+ for i in allIndices {
902
+ for j in allIndices {
903
+ guard i <= j else { continue }
904
+ let si = scalarMap [ i] !. index
905
+ let sj = scalarMap [ j] !. index
906
+
907
+ // Check String.replaceSubrange(_:with:)
908
+ do {
909
+ let replacement = " x "
910
+
911
+ var expected = " " . unicodeScalars
912
+ expected += string. unicodeScalars [ ..< si]
913
+ expected += replacement. unicodeScalars
914
+ expected += string. unicodeScalars [ sj... ]
915
+
916
+ var actual = string
917
+ actual. replaceSubrange ( i ..< j, with: replacement)
918
+
919
+ expectEqual ( actual, String ( expected) ,
920
+ """
921
+ string: \( string. debugDescription)
922
+ i: \( i)
923
+ j: \( j)
924
+ """ )
925
+ }
926
+
927
+ // Check String.unicodeScalars.replaceSubrange(_:with:)
928
+ do {
929
+ let replacement = " x " . unicodeScalars
930
+
931
+ var expected = " " . unicodeScalars
932
+ expected += string. unicodeScalars [ ..< si]
933
+ expected += replacement
934
+ expected += string. unicodeScalars [ sj... ]
935
+
936
+ var actual = string
937
+ actual. unicodeScalars. replaceSubrange ( i ..< j, with: replacement)
938
+
939
+ expectEqual ( actual, String ( expected) ,
940
+ """
941
+ string: \( string. debugDescription)
942
+ i: \( i)
943
+ j: \( j)
944
+ """ )
945
+ }
946
+ }
947
+ }
948
+ }
949
+
950
+ suite. test ( " Substring.replaceSubrange index validation " )
951
+ . forEach ( in: examples) { string in
952
+ guard #available( SwiftStdlib 5 . 7 , * ) else {
953
+ // Index navigation in 5.7 always rounds input indices down to the nearest
954
+ // Character, so that we always have a well-defined distance between
955
+ // indices, even if they aren't valid.
956
+ //
957
+ // 5.6 and below did not behave consistently in this case.
958
+ return
959
+ }
960
+
961
+ dumpIndices ( string)
962
+
963
+ let scalarMap = string. scalarMap ( )
964
+ let allIndices = string. allIndices ( )
965
+
966
+ for i in allIndices {
967
+ for j in allIndices {
968
+ guard i <= j else { continue }
969
+ let si = scalarMap [ i] !. index
970
+ let sj = scalarMap [ j] !. index
971
+
972
+ let substring = string [ i ..< j]
973
+
974
+ let subindices = substring. allIndices ( )
975
+ for m in subindices {
976
+ for n in subindices {
977
+ guard m <= n else { continue }
978
+ let sm = scalarMap [ m] !. index
979
+ let sn = scalarMap [ n] !. index
980
+
981
+ // Check Substring.replaceSubrange(_:with:)
982
+ do {
983
+ let replacement = " x "
984
+
985
+ var expected = " " . unicodeScalars
986
+ expected += string. unicodeScalars [ si ..< sm]
987
+ expected += replacement. unicodeScalars
988
+ expected += string. unicodeScalars [ sn ..< sj]
989
+
990
+ var actual = substring
991
+ actual. replaceSubrange ( m ..< n, with: replacement)
992
+
993
+ expectEqual ( actual, Substring ( expected [ ... ] ) ,
994
+ """
995
+ string: \( string. debugDescription)
996
+ i: \( i)
997
+ j: \( j)
998
+ m: \( m)
999
+ n: \( n)
1000
+ """ )
1001
+ }
1002
+
1003
+ // Check String.unicodeScalars.replaceSubrange(_:with:)
1004
+ do {
1005
+ let replacement = " x " . unicodeScalars
1006
+
1007
+ var expected = " " . unicodeScalars
1008
+ expected += string. unicodeScalars [ si ..< sm]
1009
+ expected += replacement
1010
+ expected += string. unicodeScalars [ sn ..< sj]
1011
+
1012
+ var actual = substring
1013
+ actual. unicodeScalars. replaceSubrange ( m ..< n, with: replacement)
1014
+
1015
+ expectEqual ( actual, Substring ( expected [ ... ] ) ,
1016
+ """
1017
+ string: \( string. debugDescription)
1018
+ i: \( i)
1019
+ j: \( j)
1020
+ m: \( m)
1021
+ n: \( n)
1022
+ substring.startIndex: \( substring. startIndex)
1023
+ substring.endIndex: \( substring. endIndex)
1024
+ actual.startIndex: \( actual. startIndex)
1025
+ actual.endIndex: \( actual. endIndex)
1026
+ """ )
1027
+ }
1028
+ }
1029
+ }
1030
+ }
1031
+ }
1032
+ }
0 commit comments