Skip to content
Merged
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
64 changes: 30 additions & 34 deletions stdlib/public/core/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
Expand Down Expand Up @@ -428,53 +428,49 @@ extension String {
return
}

/// Creates a new String with the specified capacity in UTF-8 code units then
/// calls the given closure with a buffer covering the String's uninitialized
/// memory.
/// Creates a new string with the specified capacity in UTF-8 code units, and
/// then calls the given closure with a buffer covering the string's
/// uninitialized memory.
///
/// The closure should return the number of initialized code units,
/// or 0 if it couldn't initialize the buffer (for example if the
/// requested capacity was too small).
///
/// This method replaces ill-formed UTF-8 sequences with the Unicode
/// replacement character (`"\u{FFFD}"`); This may require resizing
/// replacement character (`"\u{FFFD}"`). This may require resizing
/// the buffer beyond its original capacity.
///
/// The following examples use this initializer with the contents of two
/// different `UInt8` arrays---the first with well-formed UTF-8 code unit
/// sequences and the second with an ill-formed sequence at the end.
/// different `UInt8` arrays---the first with a well-formed UTF-8 code unit
/// sequence, and the second with an ill-formed sequence at the end.
///
/// let validUTF8: [UInt8] = [67, 97, 102, -61, -87, 0]
/// let s = String(unsafeUninitializedCapacity: validUTF8.count,
/// initializingUTF8With: { ptr in
/// ptr.initializeFrom(validUTF8)
/// let validUTF8: [UInt8] = [0x43, 0x61, 0x66, 0xC3, 0xA9]
/// let invalidUTF8: [UInt8] = [0x43, 0x61, 0x66, 0xC3]
///
/// let cafe1 = String(unsafeUninitializedCapacity: validUTF8.count) {
/// _ = $0.initialize(from: validUTF8)
/// return validUTF8.count
/// })
/// // Prints "Café"
/// }
/// // cafe1 == "Café"
///
/// let invalidUTF8: [UInt8] = [67, 97, 102, -61, 0]
/// let s = String(unsafeUninitializedCapacity: invalidUTF8.count,
/// initializingUTF8With: { ptr in
/// ptr.initializeFrom(invalidUTF8)
/// let cafe2 = String(unsafeUninitializedCapacity: invalidUTF8.count) {
/// _ = $0.initialize(from: invalidUTF8)
/// return invalidUTF8.count
/// })
/// // Prints "Caf�"
/// }
/// // cafe2 == "Caf�"
///
/// let s = String(unsafeUninitializedCapacity: invalidUTF8.count,
/// initializingUTF8With: { ptr in
/// ptr.initializeFrom(invalidUTF8)
/// let empty = String(unsafeUninitializedCapacity: 16) { _ in
/// // Can't initialize the buffer (e.g. the capacity is too small).
/// return 0
/// })
/// // Prints ""
/// }
/// // empty == ""
///
/// - Parameters:
/// - capacity: The number of UTF-8 code units worth of memory to allocate
/// for the String.
/// - initializer: A closure that initializes elements and sets the count of
/// the new String
/// - Parameters:
/// - buffer: A buffer covering uninitialized memory with room for the
/// specified number of UTF-8 code units.
/// for the string (excluding the null terminator).
/// - initializer: A closure that accepts a buffer covering uninitialized
/// memory with room for `capacity` UTF-8 code units, initializes
/// that memory, and returns the number of initialized elements.
@inline(__always)
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
public init(
Expand All @@ -484,14 +480,14 @@ extension String {
) throws -> Int
) rethrows {
self = try String(
uninitializedCapacity: capacity,
_uninitializedCapacity: capacity,
initializingUTF8With: initializer
)
}

@inline(__always)
internal init(
uninitializedCapacity capacity: Int,
_uninitializedCapacity capacity: Int,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, this seems like it will be a pattern we follow when we introduce new, availability-guarded public functionality that we also want to use internally.

initializingUTF8With initializer: (
_ buffer: UnsafeMutableBufferPointer<UInt8>
) throws -> Int
Expand Down Expand Up @@ -800,7 +796,7 @@ extension String {
public func lowercased() -> String {
if _fastPath(_guts.isFastASCII) {
return _guts.withFastUTF8 { utf8 in
return String(uninitializedCapacity: utf8.count) { buffer in
return String(_uninitializedCapacity: utf8.count) { buffer in
for i in 0 ..< utf8.count {
buffer[i] = _lowercaseASCII(utf8[i])
}
Expand Down Expand Up @@ -860,7 +856,7 @@ extension String {
public func uppercased() -> String {
if _fastPath(_guts.isFastASCII) {
return _guts.withFastUTF8 { utf8 in
return String(uninitializedCapacity: utf8.count) { buffer in
return String(_uninitializedCapacity: utf8.count) { buffer in
for i in 0 ..< utf8.count {
buffer[i] = _uppercaseASCII(utf8[i])
}
Expand Down
6 changes: 3 additions & 3 deletions stdlib/public/core/StringGutsRangeReplaceable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
Expand Down Expand Up @@ -97,7 +97,7 @@ extension _StringGuts {

@inline(never) // slow-path
private mutating func _foreignGrow(_ n: Int) {
let newString = String(uninitializedCapacity: n) { buffer in
let newString = String(_uninitializedCapacity: n) { buffer in
guard let count = _foreignCopyUTF8(into: buffer) else {
fatalError("String capacity was smaller than required")
}
Expand Down Expand Up @@ -161,7 +161,7 @@ extension _StringGuts {
@inline(never)
@_effects(readonly)
private func _foreignConvertedToSmall() -> _SmallString {
let smol = String(uninitializedCapacity: _SmallString.capacity) { buffer in
let smol = String(_uninitializedCapacity: _SmallString.capacity) { buffer in
guard let count = _foreignCopyUTF8(into: buffer) else {
fatalError("String capacity was smaller than required")
}
Expand Down
52 changes: 39 additions & 13 deletions test/stdlib/StringCreate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,20 @@ defer { runAllTests() }

var StringCreateTests = TestSuite("StringCreateTests")

enum SimpleString: String {
enum SimpleString: String, CaseIterable {
case smallASCII = "abcdefg"
case smallUnicode = "abéÏ𓀀"
case largeASCII = "012345678901234567890"
case largeUnicode = "abéÏ012345678901234567890𓀀"
case emoji = "😀😃🤢🤮👩🏿‍🎤🧛🏻‍♂️🧛🏻‍♂️👩‍👩‍👦‍👦"
case empty = ""
}

let simpleStrings: [String] = [
SimpleString.smallASCII.rawValue,
SimpleString.smallUnicode.rawValue,
SimpleString.largeASCII.rawValue,
SimpleString.largeUnicode.rawValue,
SimpleString.emoji.rawValue,
"",
]

extension String {
var utf32: [UInt32] { return unicodeScalars.map { $0.value } }
}

StringCreateTests.test("String(decoding:as)") {
StringCreateTests.test("String(decoding:as:)") {
func validateDecodingAs(_ str: String) {
// Non-contiguous (maybe) storage
expectEqual(str, String(decoding: str.utf8, as: UTF8.self))
Expand All @@ -41,8 +33,8 @@ StringCreateTests.test("String(decoding:as)") {

}

for str in simpleStrings {
validateDecodingAs(str)
for simpleString in SimpleString.allCases {
validateDecodingAs(simpleString.rawValue)
}

// Corner-case: UBP with null pointer (https://bugs.swift.org/browse/SR-9869)
Expand All @@ -54,3 +46,37 @@ StringCreateTests.test("String(decoding:as)") {
"", String(decoding: UnsafeBufferPointer(_empty: ()), as: UTF32.self))
}

if #available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) {
StringCreateTests.test("String(unsafeUninitializedCapacity:initializingUTF8With:)") {
for simpleString in SimpleString.allCases {
let expected = simpleString.rawValue
let expectedUTF8 = expected.utf8
let actual = String(unsafeUninitializedCapacity: expectedUTF8.count) {
_ = $0.initialize(from: expectedUTF8)
return expectedUTF8.count
}
expectEqual(expected, actual)
}

let validUTF8: [UInt8] = [0x43, 0x61, 0x66, 0xC3, 0xA9]
let invalidUTF8: [UInt8] = [0x43, 0x61, 0x66, 0xC3]

let cafe1 = String(unsafeUninitializedCapacity: validUTF8.count) {
_ = $0.initialize(from: validUTF8)
return validUTF8.count
}
expectEqual("Café", cafe1)

let cafe2 = String(unsafeUninitializedCapacity: invalidUTF8.count) {
_ = $0.initialize(from: invalidUTF8)
return invalidUTF8.count
}
expectEqual("Caf�", cafe2)

let empty = String(unsafeUninitializedCapacity: 16) { _ in
// Can't initialize the buffer (e.g. the capacity is too small).
return 0
}
expectTrue(empty.isEmpty)
}
}