Skip to content

Commit a51e32a

Browse files
committed
[stdlib] String API revisions
- Clarify StringProtocol conformance - Deprecate ExpressibleByStringInterpolation - String index conversions docs - Describe shared string indices
1 parent beeb816 commit a51e32a

File tree

5 files changed

+102
-64
lines changed

5 files changed

+102
-64
lines changed

stdlib/public/core/CompilerProtocols.swift

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -673,30 +673,8 @@ public protocol ExpressibleByDictionaryLiteral {
673673
/// Conforming to the ExpressibleByStringInterpolation Protocol
674674
/// ===========================================================
675675
///
676-
/// To use string interpolation to initialize instances of your custom type,
677-
/// implement the required initializers for `ExpressibleByStringInterpolation`
678-
/// conformance. String interpolation is a multiple-step initialization
679-
/// process. When you use string interpolation, the following steps occur:
680-
///
681-
/// 1. The string literal is broken into pieces. Each segment of the string
682-
/// literal before, between, and after any included expressions, along with
683-
/// the individual expressions themselves, are passed to the
684-
/// `init(stringInterpolationSegment:)` initializer.
685-
/// 2. The results of those calls are passed to the
686-
/// `init(stringInterpolation:)` initializer in the order in which they
687-
/// appear in the string literal.
688-
///
689-
/// In other words, initializing the `message` constant in the example above
690-
/// using string interpolation is equivalent to the following code:
691-
///
692-
/// let message = String(stringInterpolation:
693-
/// String(stringInterpolationSegment: "One cookie: $"),
694-
/// String(stringInterpolationSegment: price),
695-
/// String(stringInterpolationSegment: ", "),
696-
/// String(stringInterpolationSegment: number),
697-
/// String(stringInterpolationSegment: " cookies: $"),
698-
/// String(stringInterpolationSegment: price * number),
699-
/// String(stringInterpolationSegment: "."))
676+
/// The `ExpressibleByStringInterpolation` protocol is deprecated. Do not add
677+
/// new conformances to the protocol.
700678
@available(*, deprecated, message: "it will be replaced or redesigned in Swift 4.0. Instead of conforming to 'ExpressibleByStringInterpolation', consider adding an 'init(_:String)'")
701679
public typealias ExpressibleByStringInterpolation = _ExpressibleByStringInterpolation
702680
public protocol _ExpressibleByStringInterpolation {

stdlib/public/core/String.swift

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
import SwiftShims
1414

1515
/// A type that can represent a string as a collection of characters.
16+
///
17+
/// Do not declare new conformances to `StringProtocol`. Only the `String` and
18+
/// `Substring` types in the standard library are valid conforming types.
1619
public protocol StringProtocol
1720
: BidirectionalCollection,
1821
TextOutputStream, TextOutputStreamable,
@@ -536,10 +539,10 @@ extension String {
536539
/// // Prints "1"
537540
///
538541
/// On the other hand, an emoji flag character is constructed from a pair of
539-
/// Unicode scalar values, like `"\u{1F1F5}"` and `"\u{1F1F7}"`. Each of
540-
/// these scalar values, in turn, is too large to fit into a single UTF-16 or
541-
/// UTF-8 code unit. As a result, each view of the string `"🇵🇷"` reports a
542-
/// different length.
542+
/// Unicode scalar values, like `"\u{1F1F5}"` and `"\u{1F1F7}"`. Each of these
543+
/// scalar values, in turn, is too large to fit into a single UTF-16 or UTF-8
544+
/// code unit. As a result, each view of the string `"🇵🇷"` reports a different
545+
/// length.
543546
///
544547
/// let flag = "🇵🇷"
545548
/// print(flag.count)
@@ -561,22 +564,54 @@ extension String {
561564
///
562565
/// To find individual elements of a string, use the appropriate view for your
563566
/// task. For example, to retrieve the first word of a longer string, you can
564-
/// search the `characters` view for a space and then create a new string from
565-
/// a prefix of the `characters` view up to that point.
567+
/// search the string for a space and then create a new string from a prefix
568+
/// of the string up to that point.
566569
///
567570
/// let name = "Marie Curie"
568571
/// let firstSpace = name.index(of: " ") ?? name.endIndex
569572
/// let firstName = name[..<firstSpace]
570573
/// print(firstName)
571574
/// // Prints "Marie"
572575
///
573-
/// You can convert an index into one of a string's views to an index into
574-
/// another view.
576+
/// Strings and their views share indices, so you can access the UTF-8 view of
577+
/// the `name` string using the same `firstSpace` index.
575578
///
576-
/// let firstSpaceUTF8 = firstSpace.samePosition(in: name.utf8)
577-
/// print(Array(name.utf8[..<firstSpaceUTF8]))
579+
/// print(Array(name.utf8[..<firstSpace]))
578580
/// // Prints "[77, 97, 114, 105, 101]"
579581
///
582+
/// Note that an index into one view may not have an exact corresponding
583+
/// position in another view. For example, the `flag` string declared above
584+
/// comprises a single character, but is composed of eight code units when
585+
/// encoded as UTF-8. The following code creates constants for the first and
586+
/// second positions in the `flag.utf8` view. Accessing the `utf8` view with
587+
/// these indices yields the first and second code UTF-8 units.
588+
///
589+
/// let firstCodeUnit = flag.startIndex
590+
/// let secondCodeUnit = flag.utf8.index(after: firstCodeUnit)
591+
/// // flag.utf8[firstCodeUnit] == 240
592+
/// // flag.utf8[secondCodeUnit] == 159
593+
///
594+
/// When used to access the elements of the `flag` string itself, however, the
595+
/// `secondCodeUnit` index does not correspond to the position of a specific
596+
/// character. Instead of only accessing the specific UTF-8 code unit, that
597+
/// index is treated as the position of the character at the index's encoded
598+
/// offset. In the case of `secondCodeUnit`, that character is still the flag
599+
/// itself.
600+
///
601+
/// // flag[firstCodeUnit] == "🇵🇷"
602+
/// // flag[secondCodeUnit] == "🇵🇷"
603+
///
604+
/// If you need to validate that an index from one string's view corresponds
605+
/// with an exact position in another view, use the index's
606+
/// `samePosition(in:)` method or the `init(_:within:)` initializer.
607+
///
608+
/// if let exactIndex = secondCodeUnit.samePosition(in: flag) {
609+
/// print(flag[exactIndex])
610+
/// } else {
611+
/// print("No exact match for this position.")
612+
/// }
613+
/// // Prints "No exact match for this position."
614+
///
580615
/// Performance Optimizations
581616
/// =========================
582617
///

stdlib/public/core/StringIndexConversions.swift

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@
1212

1313
extension String.Index {
1414
/// Creates an index in the given string that corresponds exactly to the
15-
/// specified `UnicodeScalarView` position.
15+
/// specified position.
16+
///
17+
/// If the index passed as `sourcePosition` represents the start of an
18+
/// extended grapheme cluster---the element type of a string---then the
19+
/// initializer succeeds. If the index instead represents the position of a
20+
/// Unicode scalar within an extended grapheme cluster or the position of an
21+
/// encoded Unicode scalar value, the result is `nil`.
1622
///
1723
/// The following example converts the position of the Unicode scalar `"e"`
1824
/// into its corresponding position in the string. The character at that
@@ -28,21 +34,23 @@ extension String.Index {
2834
/// print(cafe[...stringIndex])
2935
/// // Prints "Café"
3036
///
31-
/// If the position passed in `unicodeScalarIndex` doesn't have an exact
32-
/// corresponding position in `other`, the result of the initializer is
37+
/// If the index passed as `sourcePosition` doesn't have an exact
38+
/// corresponding position in `target`, the result of the initializer is
3339
/// `nil`. For example, an attempt to convert the position of the combining
3440
/// acute accent (`"\u{0301}"`) fails. Combining Unicode scalars do not have
3541
/// their own position in a string.
3642
///
37-
/// let nextIndex = String.Index(cafe.unicodeScalars.index(after: scalarsIndex),
38-
/// within: cafe)
43+
/// let nextScalarsIndex = cafe.unicodeScalars.index(after: scalarsIndex)
44+
/// let nextStringIndex = String.Index(nextScalarsIndex, within: cafe)
45+
///
3946
/// print(nextIndex)
4047
/// // Prints "nil"
4148
///
4249
/// - Parameters:
43-
/// - sourcePosition: A position in (a view of) the `other` parameter.
44-
/// - target: The string referenced by both `unicodeScalarIndex` and the
45-
/// resulting index.
50+
/// - sourcePosition: A position in a view of the `target` parameter.
51+
/// `sourcePosition` must be a valid index of at least one of the views
52+
/// of `target`.
53+
/// - target: The string referenced by the resulting index.
4654
public init?(
4755
_ sourcePosition: String.Index,
4856
within target: String
@@ -57,20 +65,23 @@ extension String.Index {
5765
/// Returns the position in the given UTF-8 view that corresponds exactly to
5866
/// this index.
5967
///
60-
/// The index must be a valid index of `String(utf8)`.
61-
///
62-
/// This example first finds the position of the character `"é"` and then uses
63-
/// this method find the same position in the string's `utf8` view.
68+
/// This example first finds the position of the character `"é"`, and then
69+
/// uses this method find the same position in the string's `utf8` view.
6470
///
6571
/// let cafe = "Café"
6672
/// if let i = cafe.index(of: "é") {
67-
/// let j = i.samePosition(in: cafe.utf8)
73+
/// let j = i.samePosition(in: cafe.utf8)!
6874
/// print(Array(cafe.utf8[j...]))
6975
/// }
7076
/// // Prints "[195, 169]"
7177
///
72-
/// - Parameter utf8: The view to use for the index conversion.
78+
/// - Parameter utf8: The view to use for the index conversion. This index
79+
/// must be a valid index of at least one view of the string shared by
80+
/// `utf8`.
7381
/// - Returns: The position in `utf8` that corresponds exactly to this index.
82+
/// If this index does not have an exact corresponding position in `utf8`,
83+
/// this method returns `nil`. For example, an attempt to convert the
84+
/// position of a UTF-16 trailing surrogate returns `nil`.
7485
public func samePosition(
7586
in utf8: String.UTF8View
7687
) -> String.UTF8View.Index? {
@@ -82,18 +93,23 @@ extension String.Index {
8293
///
8394
/// The index must be a valid index of `String(utf16)`.
8495
///
85-
/// This example first finds the position of the character `"é"` and then uses
86-
/// this method find the same position in the string's `utf16` view.
96+
/// This example first finds the position of the character `"é"` and then
97+
/// uses this method find the same position in the string's `utf16` view.
8798
///
8899
/// let cafe = "Café"
89100
/// if let i = cafe.index(of: "é") {
90-
/// let j = i.samePosition(in: cafe.utf16)
101+
/// let j = i.samePosition(in: cafe.utf16)!
91102
/// print(cafe.utf16[j])
92103
/// }
93104
/// // Prints "233"
94105
///
95-
/// - Parameter utf16: The view to use for the index conversion.
96-
/// - Returns: The position in `utf16` that corresponds exactly to this index.
106+
/// - Parameter utf16: The view to use for the index conversion. This index
107+
/// must be a valid index of at least one view of the string shared by
108+
/// `utf16`.
109+
/// - Returns: The position in `utf16` that corresponds exactly to this
110+
/// index. If this index does not have an exact corresponding position in
111+
/// `utf16`, this method returns `nil`. For example, an attempt to convert
112+
/// the position of a UTF-8 continuation byte returns `nil`.
97113
public func samePosition(
98114
in utf16: String.UTF16View
99115
) -> String.UTF16View.Index? {

stdlib/public/core/StringUTF16.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,19 +340,27 @@ extension String.UTF16View.Index {
340340
/// Creates an index in the given UTF-16 view that corresponds exactly to the
341341
/// specified string position.
342342
///
343+
/// If the index passed as `sourcePosition` represents either the start of a
344+
/// Unicode scalar value or the position of a UTF-16 trailing surrogate,
345+
/// then the initializer succeeds. If `sourcePosition` does not have an
346+
/// exact corresponding position in `target`, then the result is `nil`. For
347+
/// example, an attempt to convert the position of a UTF-8 continuation byte
348+
/// results in `nil`.
349+
///
343350
/// The following example finds the position of a space in a string and then
344351
/// converts that position to an index in the string's `utf16` view.
345352
///
346353
/// let cafe = "Café 🍵"
347354
///
348355
/// let stringIndex = cafe.index(of: "é")!
349-
/// let utf16Index = String.UTF16View.Index(stringIndex, within: cafe.utf16)
356+
/// let utf16Index = String.Index(stringIndex, within: cafe.utf16)!
350357
///
351358
/// print(cafe.utf16[...utf16Index])
352359
/// // Prints "Café"
353360
///
354361
/// - Parameters:
355-
/// - sourcePosition: A position in a string or one of its views
362+
/// - sourcePosition: A position in at least one of the views of the string
363+
/// shared by `target`.
356364
/// - target: The `UTF16View` in which to find the new position.
357365
public init?(
358366
_ sourcePosition: String.Index, within target: String.UTF16View
@@ -377,6 +385,8 @@ extension String.UTF16View.Index {
377385
/// // Prints "Café"
378386
///
379387
/// - Parameter unicodeScalars: The view to use for the index conversion.
388+
/// This index must be a valid index of at least one view of the string
389+
/// shared by `unicodeScalars`.
380390
/// - Returns: The position in `unicodeScalars` that corresponds exactly to
381391
/// this index. If this index does not have an exact corresponding
382392
/// position in `unicodeScalars`, this method returns `nil`. For example,

stdlib/public/core/StringUnicodeScalarView.swift

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -378,34 +378,32 @@ extension String.UnicodeScalarIndex {
378378
/// let cafe = "Café 🍵"
379379
///
380380
/// let utf16Index = cafe.utf16.index(of: 32)!
381-
/// let scalarIndex = String.UnicodeScalarView.Index(utf16Index, within: cafe.unicodeScalars)!
381+
/// let scalarIndex = String.Index(utf16Index, within: cafe.unicodeScalars)!
382382
///
383383
/// print(String(cafe.unicodeScalars[..<scalarIndex]))
384384
/// // Prints "Café"
385385
///
386-
/// If the position passed in `utf16Index` doesn't have an exact
386+
/// If the index passed as `sourcePosition` doesn't have an exact
387387
/// corresponding position in `unicodeScalars`, the result of the
388388
/// initializer is `nil`. For example, an attempt to convert the position of
389-
/// the trailing surrogate of a UTF-16 surrogate pair fails.
389+
/// the trailing surrogate of a UTF-16 surrogate pair results in `nil`.
390390
///
391391
/// - Parameters:
392-
/// - utf16Index: A position in the `utf16` view of a string. `utf16Index`
392+
/// - sourcePosition: A position in the `utf16` view of a string. `utf16Index`
393393
/// must be an element of `String(unicodeScalars).utf16.indices`.
394394
/// - unicodeScalars: The `UnicodeScalarView` in which to find the new
395395
/// position.
396396
public init?(
397-
_ utf16Index: String.UTF16Index,
397+
_ sourcePosition: String.UTF16Index,
398398
within unicodeScalars: String.UnicodeScalarView
399399
) {
400-
if !unicodeScalars._isOnUnicodeScalarBoundary(utf16Index) { return nil }
401-
self = utf16Index
400+
if !unicodeScalars._isOnUnicodeScalarBoundary(sourcePosition) { return nil }
401+
self = sourcePosition
402402
}
403403

404404
/// Returns the position in the given string that corresponds exactly to this
405405
/// index.
406406
///
407-
/// This index must be a valid index of `characters.unicodeScalars`.
408-
///
409407
/// This example first finds the position of a space (UTF-8 code point `32`)
410408
/// in a string's `utf8` view and then uses this method find the same position
411409
/// in the string.
@@ -417,6 +415,7 @@ extension String.UnicodeScalarIndex {
417415
/// // Prints "🍵"
418416
///
419417
/// - Parameter characters: The string to use for the index conversion.
418+
/// This index must be a valid index of at least one view of `characters`.
420419
/// - Returns: The position in `characters` that corresponds exactly to
421420
/// this index. If this index does not have an exact corresponding
422421
/// position in `characters`, this method returns `nil`. For example,

0 commit comments

Comments
 (0)