Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions stdlib/public/core/StringIndex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -512,3 +512,81 @@ extension String.Index: Hashable {
hasher.combine(orderingValue)
}
}

@available(SwiftStdlib 5.7, *)
extension String.Index: CustomStringConvertible {
internal var _encodingDescription: String {
switch (_rawBits & Self.__utf8Bit != 0, _rawBits & Self.__utf16Bit != 0) {
case (false, false): return "unknown"
case (true, false): return "utf8"
case (false, true): return "utf16"
case (true, true): return "any"
}
}

@available(SwiftStdlib 5.7, *)
@inline(never)
public var description: String {
// For the regular `description`, we just print the offset value along with
// its encoding and the transcoded offset, in a compact form: `23.utf8+1`.
//
// This is intended to expose crucial positioning information in a form that
// remains relatively readable even when part of a larger printout, e.g., as
// with range expressions like `12[utf8]..<56[utf8]+1`.
var d = "\(_encodedOffset)[\(_encodingDescription)]"
if transcodedOffset != 0 {
d += "+\(transcodedOffset)"
}
return d
}
}

@available(SwiftStdlib 5.7, *)
extension String.Index: CustomDebugStringConvertible {
@available(SwiftStdlib 5.7, *)
@inline(never)
public var debugDescription: String {
var d = "String.Index("
d += "offset: \(_encodedOffset)"
d += ", encoding: \(_encodingDescription)"
if transcodedOffset != 0 {
d += ", transcodedOffset: \(transcodedOffset)"
}
if _isCharacterAligned {
d += ", aligned: character"
} else if _isScalarAligned {
d += ", aligned: scalar"
}
if let stride = characterStride {
d += ", stride: \(stride)"
}
d += ")"
return d
}
}

#if SWIFT_ENABLE_REFLECTION
@available(SwiftStdlib 5.7, *)
extension String.Index: CustomReflectable {
@available(SwiftStdlib 5.7, *)
@inline(never)
public var customMirror: Mirror {
var children: [(label: String?, value: Any)] = []
children.reserveCapacity(5)
children.append(("_offset", _encodedOffset))
children.append(("_encoding", _encodingDescription))
if transcodedOffset > 0 {
children.append(("_transcodedOffset", transcodedOffset))
}
if _isCharacterAligned {
children.append(("_aligned", "character"))
} else if _isScalarAligned {
children.append(("_aligned", "scalar"))
}
if let stride = characterStride {
children.append(("_stride", stride))
}
return Mirror(self, children: children, displayStyle: .struct)
}
}
#endif
7 changes: 6 additions & 1 deletion test/api-digester/stability-stdlib-abi-without-asserts.test
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ Func Collection.subranges(where:) has been removed
Func MutableCollection.moveSubranges(_:to:) has been removed
Func MutableCollection.removeSubranges(_:) has been removed
Func RangeReplaceableCollection.removeSubranges(_:) has been removed
Struct AnyHashable has added a conformance to an existing protocol _HasCustomAnyHashableRepresentation
Struct DiscontiguousSlice has been removed
Struct RangeSet has been removed
Subscript Collection.subscript(_:) has been removed
Expand All @@ -67,6 +66,12 @@ Protocol Error has added inherited protocol Sendable
Protocol Error has generic signature change from to <Self : Swift.Sendable>
Enum Never has added a conformance to an existing protocol Identifiable

// The ABI checker doesn't understand protocol conformances with availability
// yet.
Struct AnyHashable has added a conformance to an existing protocol _HasCustomAnyHashableRepresentation
Struct String.Index has added a conformance to an existing protocol CustomStringConvertible
Struct String.Index has added a conformance to an existing protocol CustomDebugStringConvertible

// These haven't actually been removed; they are simply marked unavailable.
// This seems to be a false positive in the ABI checker. This is not an ABI
// break because the symbols are still present, and is not a source break
Expand Down
252 changes: 252 additions & 0 deletions test/stdlib/StringIndex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,258 @@ let simpleStrings: [String] = [
"",
]

suite.test("CustomStringConvertible/native") {
guard #available(SwiftStdlib 5.7, *) else { return }

let string = "a👉🏼b"

func check<S: Collection>(
_ s: S,
_ expected: [String],
file: String = #file, line: UInt = #line
) where S.Index == String.Index {
let indices = Array(s.indices) + [s.endIndex]
expectEqual(
indices.count,
expected.count,
"count",
file: file, line: line)
for i in 0 ..< indices.count {
expectEqual(
"\(indices[i])",
expected[i],
"i: \(i)",
file: file, line: line)
}
}

check(string, [
"0[any]",
"1[utf8]",
"9[utf8]",
"10[utf8]",
])
check(string.unicodeScalars, [
"0[any]",
"1[utf8]",
"5[utf8]",
"9[utf8]",
"10[utf8]",
])
check(string.utf8, [
"0[any]",
"1[utf8]",
"2[utf8]",
"3[utf8]",
"4[utf8]",
"5[utf8]",
"6[utf8]",
"7[utf8]",
"8[utf8]",
"9[utf8]",
"10[utf8]",
])
check(string.utf16, [
"0[any]",
"1[utf8]",
"1[utf8]+1",
"5[utf8]",
"5[utf8]+1",
"9[utf8]",
"10[utf8]",
])
}

#if _runtime(_ObjC)
suite.test("CustomStringConvertible/bridged") {
guard #available(SwiftStdlib 5.7, *) else { return }

let string = "a👉🏼b" as NSString as String

func check<S: Collection>(
_ s: S,
_ expected: [String],
file: String = #file, line: UInt = #line
) where S.Index == String.Index {
let indices = Array(s.indices) + [s.endIndex]
expectEqual(
indices.count,
expected.count,
"count",
file: file, line: line)
for i in 0 ..< indices.count {
expectEqual(
"\(indices[i])",
expected[i],
"i: \(i)",
file: file, line: line)
}
}

check(string, [
"0[any]",
"1[utf16]",
"5[utf16]",
"6[utf16]",
])
check(string.unicodeScalars, [
"0[any]",
"1[utf16]",
"3[utf16]",
"5[utf16]",
"6[utf16]",
])
check(string.utf8, [
"0[any]",
"1[utf16]",
"1[utf16]+1",
"1[utf16]+2",
"1[utf16]+3",
"3[utf16]",
"3[utf16]+1",
"3[utf16]+2",
"3[utf16]+3",
"5[utf16]",
"6[utf16]",
])
check(string.utf16, [
"0[any]",
"1[utf16]",
"2[utf16]",
"3[utf16]",
"4[utf16]",
"5[utf16]",
"6[utf16]",
])
}
#endif

suite.test("CustomDebugStringConvertible/native") {
guard #available(SwiftStdlib 5.7, *) else { return }

let string = "a👉🏼b"

func check<S: Collection>(
_ s: S,
_ expected: [String],
file: String = #file, line: UInt = #line
) where S.Index == String.Index {
let indices = Array(s.indices) + [s.endIndex]
expectEqual(
indices.count,
expected.count,
"count",
file: file, line: line)
for i in 0 ..< indices.count {
expectEqual(
String(reflecting: indices[i]),
expected[i],
"i: \(i)",
file: file, line: line)
}
}

check(string, [
"String.Index(offset: 0, encoding: any, aligned: character)",
"String.Index(offset: 1, encoding: utf8, aligned: character, stride: 8)",
"String.Index(offset: 9, encoding: utf8, aligned: character, stride: 1)",
"String.Index(offset: 10, encoding: utf8, aligned: character)",
])
check(string.unicodeScalars, [
"String.Index(offset: 0, encoding: any, aligned: character)",
"String.Index(offset: 1, encoding: utf8, aligned: scalar)",
"String.Index(offset: 5, encoding: utf8, aligned: scalar)",
"String.Index(offset: 9, encoding: utf8, aligned: scalar)",
"String.Index(offset: 10, encoding: utf8, aligned: character)",
])
check(string.utf8, [
"String.Index(offset: 0, encoding: any, aligned: character)",
"String.Index(offset: 1, encoding: utf8)",
"String.Index(offset: 2, encoding: utf8)",
"String.Index(offset: 3, encoding: utf8)",
"String.Index(offset: 4, encoding: utf8)",
"String.Index(offset: 5, encoding: utf8)",
"String.Index(offset: 6, encoding: utf8)",
"String.Index(offset: 7, encoding: utf8)",
"String.Index(offset: 8, encoding: utf8)",
"String.Index(offset: 9, encoding: utf8)",
"String.Index(offset: 10, encoding: utf8, aligned: character)",
])
check(string.utf16, [
"String.Index(offset: 0, encoding: any, aligned: character)",
"String.Index(offset: 1, encoding: utf8, aligned: scalar)",
"String.Index(offset: 1, encoding: utf8, transcodedOffset: 1)",
"String.Index(offset: 5, encoding: utf8, aligned: scalar)",
"String.Index(offset: 5, encoding: utf8, transcodedOffset: 1)",
"String.Index(offset: 9, encoding: utf8, aligned: scalar)",
"String.Index(offset: 10, encoding: utf8, aligned: character)",
])
}

#if _runtime(_ObjC)
suite.test("CustomDebugStringConvertible/bridged") {
guard #available(SwiftStdlib 5.7, *) else { return }

let string = "a👉🏼b" as NSString as String

func check<S: Collection>(
_ s: S,
_ expected: [String],
file: String = #file, line: UInt = #line
) where S.Index == String.Index {
let indices = Array(s.indices) + [s.endIndex]
expectEqual(
indices.count,
expected.count,
"count",
file: file, line: line)
for i in 0 ..< indices.count {
expectEqual(
String(reflecting: indices[i]),
expected[i],
"i: \(i)",
file: file, line: line)
}
}

check(string, [
"String.Index(offset: 0, encoding: any, aligned: character)",
"String.Index(offset: 1, encoding: utf16, aligned: character, stride: 4)",
"String.Index(offset: 5, encoding: utf16, aligned: character, stride: 1)",
"String.Index(offset: 6, encoding: utf16, aligned: character)",
])
check(string.unicodeScalars, [
"String.Index(offset: 0, encoding: any, aligned: character)",
"String.Index(offset: 1, encoding: utf16, aligned: scalar)",
"String.Index(offset: 3, encoding: utf16, aligned: scalar)",
"String.Index(offset: 5, encoding: utf16, aligned: scalar)",
"String.Index(offset: 6, encoding: utf16, aligned: character)",
])
check(string.utf8, [
"String.Index(offset: 0, encoding: any, aligned: character)",
"String.Index(offset: 1, encoding: utf16, aligned: scalar)",
"String.Index(offset: 1, encoding: utf16, transcodedOffset: 1)",
"String.Index(offset: 1, encoding: utf16, transcodedOffset: 2)",
"String.Index(offset: 1, encoding: utf16, transcodedOffset: 3)",
"String.Index(offset: 3, encoding: utf16, aligned: scalar)",
"String.Index(offset: 3, encoding: utf16, transcodedOffset: 1)",
"String.Index(offset: 3, encoding: utf16, transcodedOffset: 2)",
"String.Index(offset: 3, encoding: utf16, transcodedOffset: 3)",
"String.Index(offset: 5, encoding: utf16, aligned: scalar)",
"String.Index(offset: 6, encoding: utf16, aligned: character)",
])
check(string.utf16, [
"String.Index(offset: 0, encoding: any, aligned: character)",
"String.Index(offset: 1, encoding: utf16)",
"String.Index(offset: 2, encoding: utf16)",
"String.Index(offset: 3, encoding: utf16)",
"String.Index(offset: 4, encoding: utf16)",
"String.Index(offset: 5, encoding: utf16)",
"String.Index(offset: 6, encoding: utf16, aligned: character)",
])
}
#endif

suite.test("basic sanity checks")
.forEach(in: simpleStrings) { s in
let utf8 = Array(s.utf8)
Expand Down