// RUN: %empty-directory(%t) // RUN: %target-build-swift -g %s -o %t/StringIndex // RUN: %target-codesign %t/StringIndex // RUN: env %env-SWIFT_BINARY_COMPATIBILITY_VERSION=0x050700 %target-run %t/StringIndex %S/Inputs/ // Note: the environment variable above forces the stdlib's bincompat version to // 5.7 so that we can test new behavior even if the SDK we're using predates it. // REQUIRES: executable_test // UNSUPPORTED: freestanding import StdlibUnittest #if _runtime(_ObjC) import Foundation import StdlibUnicodeUnittest #endif var suite = TestSuite("StringIndexTests") defer { runAllTests() } enum SimpleString: String { case smallASCII = "abcdefg" case smallUnicode = "abรฉร๐“€€" case largeASCII = "012345678901234567890" case largeUnicode = "abรฉร012345678901234567890๐“€€" case emoji = "๐Ÿ˜€๐Ÿ˜ƒ๐Ÿคข๐Ÿคฎ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽค๐Ÿง›๐Ÿปโ€โ™‚๏ธ๐Ÿง›๐Ÿปโ€โ™‚๏ธ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ" } let simpleStrings: [String] = [ SimpleString.smallASCII.rawValue, SimpleString.smallUnicode.rawValue, SimpleString.largeASCII.rawValue, SimpleString.largeUnicode.rawValue, SimpleString.emoji.rawValue, "", ] suite.test("basic sanity checks") .forEach(in: simpleStrings) { s in let utf8 = Array(s.utf8) let subUTF8 = Array(s[...].utf8) let utf16 = Array(s.utf16) let subUTF16 = Array(s[...].utf16) let utf32 = Array(s.unicodeScalars.map { $0.value }) let subUTF32 = Array(s[...].unicodeScalars.map { $0.value }) expectEqual(s, String(decoding: utf8, as: UTF8.self)) expectEqual(s, String(decoding: subUTF8, as: UTF8.self)) expectEqual(s, String(decoding: utf16, as: UTF16.self)) expectEqual(s, String(decoding: subUTF16, as: UTF16.self)) expectEqual(s, String(decoding: utf32, as: UTF32.self)) expectEqual(s, String(decoding: subUTF32, as: UTF32.self)) } suite.test("view counts") .forEach(in: simpleStrings) { s in func validateViewCount( _ view: View, for string: String, stackTrace: SourceLocStack = SourceLocStack(), showFrame: Bool = true, file: String = #file, line: UInt = #line ) where View.Element: Equatable, View.Index == String.Index { let stackTrace = stackTrace.pushIf(showFrame, file: file, line: line) let count = view.count func expect(_ i: Int, file: String = #file, line: UInt = #line ) { expectEqual(count, i, "for String: \(string)", stackTrace: stackTrace.pushIf(showFrame, file: file, line: line), showFrame: false) } let reversedView = view.reversed() expect(Array(view).count) expect(view.indices.count) expect(view.indices.reversed().count) expect(reversedView.indices.count) expect(view.distance(from: view.startIndex, to: view.endIndex)) expect(reversedView.distance( from: reversedView.startIndex, to: reversedView.endIndex)) // Access the elements from the indices expectEqual(Array(view), view.indices.map { view[$0] }) expectEqual( Array(reversedView), reversedView.indices.map { reversedView[$0] }) let indicesArray = Array(view.indices) for i in 0..= 0b1100_0000 { expectEqual(idx, idx.samePosition(in: s.unicodeScalars)) expectEqual(idx, idx.samePosition(in: s.utf16)) // ASCII if char <= 0x7F { expectEqual(UInt16(char), s.utf16[idx]) expectEqual(UInt32(char), s.unicodeScalars[idx].value) } } else { // Continuation code unit assert(char & 0b1100_0000 == 0b1000_0000) expectNil(idx.samePosition(in: s)) expectNil(idx.samePosition(in: s.utf16)) expectNil(idx.samePosition(in: s.unicodeScalars)) } } } suite.test("UTF-16 Offsets") .forEach(in: simpleStrings) { s in let end = s.endIndex let utf16Count = s.utf16.count expectEqual(end, String.Index(utf16Offset: utf16Count, in: s)) expectEqual(end, String.Index(utf16Offset: utf16Count, in: s[...])) let pastEnd = String.Index(utf16Offset: utf16Count+1, in: s) expectNotEqual(end, pastEnd) expectEqual(pastEnd, String.Index(utf16Offset: utf16Count+1, in: s[...])) expectEqual(pastEnd, String.Index(utf16Offset: utf16Count+2, in: s)) expectEqual(pastEnd, String.Index(utf16Offset: -1, in: s)) expectEqual( pastEnd, String.Index(utf16Offset: Swift.max(1, utf16Count), in: s.dropFirst())) let utf16Indices = Array(s.utf16.indices) expectEqual(utf16Count, utf16Indices.count) for i in 0.. String.Index { var idx = idx while str.utf8[idx] & 0xC0 == 0x80 { str.utf8.formIndex(before: &idx) } return idx } suite.test("Scalar Align UTF-8 indices") { // TODO: Test a new aligning API when we add it. For now, we // test scalar-aligning UTF-8 indices let str = "a๐Ÿ˜‡" let subScalarIdx = str.utf8.index(str.utf8.startIndex, offsetBy: 2) let roundedIdx = swift5ScalarAlign(subScalarIdx, in: str) expectEqual(1, roundedIdx.utf16Offset(in: str)) let roundedIdx2 = str.utf8[...subScalarIdx].lastIndex { $0 & 0xC0 != 0x80 } expectEqual(roundedIdx, roundedIdx2) var roundedIdx3 = subScalarIdx while roundedIdx3.samePosition(in: str.unicodeScalars) == nil { str.utf8.formIndex(before: &roundedIdx3) } expectEqual(roundedIdx, roundedIdx3) } #if _runtime(_ObjC) suite.test("String.Index(_:within) / Range(_:in:)") { guard #available(SwiftStdlib 5.1, *) else { return } let str = simpleStrings.joined() let substr = str[...] for idx in str.utf8.indices { expectEqual( String.Index(idx, within: str), String.Index(idx, within: substr)) } expectNil(String.Index(str.startIndex, within: str.dropFirst())) expectNil(String.Index(str.endIndex, within: str.dropLast())) expectNotNil(String.Index(str.startIndex, within: str)) expectNotNil(String.Index(str.endIndex, within: str)) let utf16Count = str.utf16.count let utf16Indices = Array(str.utf16.indices) + [str.utf16.endIndex] for location in 0..(nsRange, in: str) let substrRange = Range(nsRange, in: substr) expectEqual(strRange, substrRange) guard strLB != nil && strUB != nil else { expectNil(strRange) continue } expectEqual(strRange, Range(uncheckedBounds: (strLB!, strUB!))) } } } #endif #if _runtime(_ObjC) suite.test("Misaligned") { // Misaligned indices were fixed in 5.1 guard _hasSwift_5_1() else { return } func doIt(_ str: String) { let characterIndices = Array(str.indices) let scalarIndices = Array(str.unicodeScalars.indices) + [str.endIndex] let utf8Indices = Array(str.utf8.indices) var lastScalarI = 0 for i in 1.. utf8StartIdx { str.utf8.formIndex(before: &utf8RevIdx) expectEqual(curScalar, str.unicodeScalars[utf8RevIdx]) expectEqual(curSubChar, str[utf8RevIdx]) expectFalse(UTF16.isTrailSurrogate(str.utf16[utf8RevIdx])) expectEqual(utf8StartIdx, str[utf8RevIdx...].startIndex) expectTrue(str[utf8StartIdx.. utf16StartIdx { str.utf16.formIndex(before: &utf16RevIdx) expectEqual(curScalar, str.unicodeScalars[utf16RevIdx]) expectEqual(curSubChar, str[utf16RevIdx]) expectFalse(UTF8.isContinuation(str.utf8[utf16RevIdx])) expectEqual(utf16StartIdx, str[utf16RevIdx...].startIndex) expectTrue(str[utf16StartIdx.. Int { let ci = characterMap[i]! let cj = characterMap[j]! return cj.offset - ci.offset } func referenceScalarDistance( from i: String.Index, to j: String.Index ) -> Int { let si = scalarMap[i]! let sj = scalarMap[j]! return sj.offset - si.offset } for i in allIndices { for j in allIndices { let characterDistance = referenceCharacterDistance(from: i, to: j) let scalarDistance = referenceScalarDistance(from: i, to: j) // Check distance calculations. expectEqual( string.distance(from: i, to: j), characterDistance, """ string: \(string.debugDescription) i: \(i) j: \(j) """) expectEqual( string.unicodeScalars.distance(from: i, to: j), scalarDistance, """ string: \(string.debugDescription) i: \(i) j: \(j) """) if i <= j { // The substring `string[i.. 0 && i <= limit && dest > limit ? true : characterDistance < 0 && i >= limit && dest < limit ? true : false) expectEqual( string.index(i, offsetBy: characterDistance, limitedBy: limit), expectHit ? nil : dest, """ string: \(string.debugDescription) i: \(i) j: \(j) (distance: \(characterDistance)) limit: \(limit) """) } } } } suite.test("Fully exhaustive index interchange") .forEach(in: examples) { string in fullyExhaustiveIndexInterchange(string) } #if _runtime(_ObjC) suite.test("Fully exhaustive index interchange/GraphemeBreakTests") { for string in graphemeBreakTests.map { $0.0 } { fullyExhaustiveIndexInterchange(string) } } #endif suite.test("Global vs local grapheme cluster boundaries") { guard #available(SwiftStdlib 5.7, *) else { // Index navigation in 5.7 always rounds input indices down to the nearest // Character, so that we always have a well-defined distance between // indices, even if they aren't valid. // // 5.6 and below did not behave consistently in this case. return } let str = "a๐Ÿ‡บ๐Ÿ‡ธ๐Ÿ‡จ๐Ÿ‡ฆb" // U+0061 LATIN SMALL LETTER A // U+1F1FA REGIONAL INDICATOR SYMBOL LETTER U // U+1F1F8 REGIONAL INDICATOR SYMBOL LETTER S // U+1F1E8 REGIONAL INDICATOR SYMBOL LETTER C // U+1F1E6 REGIONAL INDICATOR SYMBOL LETTER A // U+0062 LATIN SMALL LETTER B let c = Array(str.indices) + [str.endIndex] let s = Array(str.unicodeScalars.indices) + [str.unicodeScalars.endIndex] let u8 = Array(str.utf8.indices) + [str.utf8.endIndex] let u16 = Array(str.utf16.indices) + [str.utf16.endIndex] // Index navigation must always round the input index down to the nearest // Character. expectEqual(str.count, 4) expectEqual(str.index(after: c[0]), c[1]) expectEqual(str.index(after: c[1]), c[2]) expectEqual(str.index(after: c[2]), c[3]) expectEqual(str.index(after: c[3]), c[4]) expectEqual(str.index(before: c[4]), c[3]) expectEqual(str.index(before: c[3]), c[2]) expectEqual(str.index(before: c[2]), c[1]) expectEqual(str.index(before: c[1]), c[0]) // Scalars expectEqual(str.unicodeScalars.count, 6) expectEqual(str.index(after: s[0]), s[1]) expectEqual(str.index(after: s[1]), s[3]) expectEqual(str.index(after: s[2]), s[3]) // s[2] โ‰… s[1] expectEqual(str.index(after: s[3]), s[5]) expectEqual(str.index(after: s[4]), s[5]) // s[4] โ‰… s[3] expectEqual(str.index(after: s[5]), s[6]) expectEqual(str.index(before: s[6]), s[5]) expectEqual(str.index(before: s[5]), s[3]) expectEqual(str.index(before: s[4]), s[1]) // s[4] โ‰… s[3] expectEqual(str.index(before: s[3]), s[1]) expectEqual(str.index(before: s[2]), s[0]) // s[2] โ‰… s[1] expectEqual(str.index(before: s[1]), s[0]) // UTF-8 expectEqual(str.utf8.count, 18) expectEqual(str.index(after: u8[0]), u8[1]) for i in 1 ..< 9 { // s[i] โ‰… s[1] expectEqual(str.index(after: u8[i]), u8[9]) } for i in 9 ..< 17 { // s[i] โ‰… s[9] expectEqual(str.index(after: u8[i]), u8[17]) } expectEqual(str.index(after: u8[17]), u8[18]) // UTF-16 expectEqual(str.utf16.count, 10) expectEqual(str.index(after: u16[0]), u16[1]) expectEqual(str.index(after: u16[1]), u16[5]) expectEqual(str.index(after: u16[2]), u16[5]) // s[2] โ‰… s[1] expectEqual(str.index(after: u16[3]), u16[5]) // s[3] โ‰… s[1] expectEqual(str.index(after: u16[4]), u16[5]) // s[4] โ‰… s[1] expectEqual(str.index(after: u16[5]), u16[9]) expectEqual(str.index(after: u16[6]), u16[9]) // s[6] โ‰… s[5] expectEqual(str.index(after: u16[7]), u16[9]) // s[7] โ‰… s[5] expectEqual(str.index(after: u16[8]), u16[9]) // s[8] โ‰… s[5] expectEqual(str.index(after: u16[9]), u16[10]) let i = s[2] // second scalar of US flag let j = s[4] // second scalar of CA flag // However, subscripting should only round down to the nearest scalar. // Per SE-0180, `s[i..