Skip to content

Commit f9b3e14

Browse files
[stdlib] Use Swift-native Character iteration for hasPrefix/Suffix (#14390)
* Use Swift-native Character iteration for hasPrefix/Suffix * Remove old tests for removed C shims
1 parent a5db086 commit f9b3e14

File tree

7 files changed

+76
-210
lines changed

7 files changed

+76
-210
lines changed

stdlib/public/core/String.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,8 @@ public protocol StringProtocol
3636
var utf16: UTF16View { get }
3737
var unicodeScalars: UnicodeScalarView { get }
3838

39-
#if _runtime(_ObjC)
4039
func hasPrefix(_ prefix: String) -> Bool
4140
func hasSuffix(_ prefix: String) -> Bool
42-
#endif
4341

4442
func lowercased() -> String
4543
func uppercased() -> String

stdlib/public/core/StringLegacy.swift

Lines changed: 76 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -84,35 +84,36 @@ extension String {
8484
}
8585
}
8686

87-
#if _runtime(_ObjC)
88-
/// Determines if `theString` starts with `prefix` comparing the strings under
89-
/// canonical equivalence.
90-
@_inlineable // FIXME(sil-serialize-all)
91-
@_versioned // FIXME(sil-serialize-all)
92-
@_silgen_name("swift_stdlib_NSStringHasPrefixNFD")
93-
internal func _stdlib_NSStringHasPrefixNFD(
94-
_ theString: AnyObject, _ prefix: AnyObject) -> Bool
9587

96-
@_inlineable // FIXME(sil-serialize-all)
97-
@_versioned // FIXME(sil-serialize-all)
98-
@_silgen_name("swift_stdlib_NSStringHasPrefixNFDPointer")
99-
internal func _stdlib_NSStringHasPrefixNFDPointer(
100-
_ theString: OpaquePointer, _ prefix: OpaquePointer) -> Bool
88+
// TODO: since this is generally useful, make public via evolution proposal.
89+
extension BidirectionalCollection {
90+
@_inlineable
91+
@_versioned
92+
internal func _ends<Suffix: BidirectionalCollection>(
93+
with suffix: Suffix, by areEquivalent: (Element,Element) -> Bool
94+
) -> Bool where Suffix.Element == Element {
95+
var (i,j) = (self.endIndex,suffix.endIndex)
96+
while i != self.startIndex, j != suffix.startIndex {
97+
self.formIndex(before: &i)
98+
suffix.formIndex(before: &j)
99+
if !areEquivalent(self[i],suffix[j]) { return false }
100+
}
101+
return j == suffix.startIndex
102+
}
103+
}
104+
105+
extension BidirectionalCollection where Element: Equatable {
106+
@_inlineable
107+
@_versioned
108+
internal func _ends<Suffix: BidirectionalCollection>(
109+
with suffix: Suffix
110+
) -> Bool where Suffix.Element == Element {
111+
return _ends(with: suffix, by: ==)
112+
}
113+
}
101114

102-
/// Determines if `theString` ends with `suffix` comparing the strings under
103-
/// canonical equivalence.
104-
@_inlineable // FIXME(sil-serialize-all)
105-
@_versioned // FIXME(sil-serialize-all)
106-
@_silgen_name("swift_stdlib_NSStringHasSuffixNFD")
107-
internal func _stdlib_NSStringHasSuffixNFD(
108-
_ theString: AnyObject, _ suffix: AnyObject) -> Bool
109-
@_inlineable // FIXME(sil-serialize-all)
110-
@_versioned // FIXME(sil-serialize-all)
111-
@_silgen_name("swift_stdlib_NSStringHasSuffixNFDPointer")
112-
internal func _stdlib_NSStringHasSuffixNFDPointer(
113-
_ theString: OpaquePointer, _ suffix: OpaquePointer) -> Bool
114115

115-
extension String {
116+
extension StringProtocol {
116117
/// Returns a Boolean value indicating whether the string begins with the
117118
/// specified prefix.
118119
///
@@ -142,40 +143,9 @@ extension String {
142143
///
143144
/// - Parameter prefix: A possible prefix to test against this string.
144145
/// - Returns: `true` if the string begins with `prefix`; otherwise, `false`.
145-
@_inlineable // FIXME(sil-serialize-all)
146-
public func hasPrefix(_ prefix: String) -> Bool {
147-
let prefixCount = prefix._guts.count
148-
if prefixCount == 0 {
149-
return true
150-
}
151-
if _fastPath(!self._guts._isOpaque && !prefix._guts._isOpaque) {
152-
let result: Bool
153-
if self._guts.isASCII && prefix._guts.isASCII {
154-
let selfASCII = self._guts._unmanagedASCIIView
155-
let prefixASCII = prefix._guts._unmanagedASCIIView
156-
if prefixASCII.count > selfASCII.count {
157-
// Prefix is longer than self.
158-
result = false
159-
} else {
160-
result = (0 as CInt) == _stdlib_memcmp(
161-
selfASCII.rawStart,
162-
prefixASCII.rawStart,
163-
prefixASCII.count)
164-
}
165-
} else {
166-
let lhsStr = _NSContiguousString(_unmanaged: self._guts)
167-
let rhsStr = _NSContiguousString(_unmanaged: prefix._guts)
168-
result = lhsStr._unsafeWithNotEscapedSelfPointerPair(rhsStr) {
169-
return _stdlib_NSStringHasPrefixNFDPointer($0, $1)
170-
}
171-
}
172-
_fixLifetime(self)
173-
_fixLifetime(prefix)
174-
return result
175-
}
176-
return _stdlib_NSStringHasPrefixNFD(
177-
self._bridgeToObjectiveCImpl(),
178-
prefix._bridgeToObjectiveCImpl())
146+
@_inlineable
147+
public func hasPrefix<Prefix: StringProtocol>(_ prefix: Prefix) -> Bool {
148+
return self.starts(with: prefix)
179149
}
180150

181151
/// Returns a Boolean value indicating whether the string ends with the
@@ -207,15 +177,52 @@ extension String {
207177
///
208178
/// - Parameter suffix: A possible suffix to test against this string.
209179
/// - Returns: `true` if the string ends with `suffix`; otherwise, `false`.
180+
@_inlineable
181+
public func hasSuffix<Suffix: StringProtocol>(_ suffix: Suffix) -> Bool {
182+
return self._ends(with: suffix)
183+
}
184+
}
185+
186+
extension String {
187+
@_inlineable // FIXME(sil-serialize-all)
188+
public func hasPrefix(_ prefix: String) -> Bool {
189+
let prefixCount = prefix._guts.count
190+
if prefixCount == 0 { return true }
191+
192+
if _fastPath(!self._guts._isOpaque && !prefix._guts._isOpaque) {
193+
if self._guts.isASCII && prefix._guts.isASCII {
194+
let result: Bool
195+
let selfASCII = self._guts._unmanagedASCIIView
196+
let prefixASCII = prefix._guts._unmanagedASCIIView
197+
if prefixASCII.count > selfASCII.count {
198+
// Prefix is longer than self.
199+
result = false
200+
} else {
201+
result = (0 as CInt) == _stdlib_memcmp(
202+
selfASCII.rawStart,
203+
prefixASCII.rawStart,
204+
prefixASCII.count)
205+
}
206+
_fixLifetime(self)
207+
_fixLifetime(prefix)
208+
return result
209+
}
210+
else {
211+
212+
}
213+
}
214+
215+
return self.starts(with: prefix)
216+
}
217+
210218
@_inlineable // FIXME(sil-serialize-all)
211219
public func hasSuffix(_ suffix: String) -> Bool {
212220
let suffixCount = suffix._guts.count
213-
if suffixCount == 0 {
214-
return true
215-
}
221+
if suffixCount == 0 { return true }
222+
216223
if _fastPath(!self._guts._isOpaque && !suffix._guts._isOpaque) {
217-
let result: Bool
218224
if self._guts.isASCII && suffix._guts.isASCII {
225+
let result: Bool
219226
let selfASCII = self._guts._unmanagedASCIIView
220227
let suffixASCII = suffix._guts._unmanagedASCIIView
221228
if suffixASCII.count > self._guts.count {
@@ -227,26 +234,15 @@ extension String {
227234
suffixASCII.rawStart,
228235
suffixASCII.count)
229236
}
230-
} else {
231-
let lhsStr = _NSContiguousString(_unmanaged: self._guts)
232-
let rhsStr = _NSContiguousString(_unmanaged: suffix._guts)
233-
result = lhsStr._unsafeWithNotEscapedSelfPointerPair(rhsStr) {
234-
return _stdlib_NSStringHasSuffixNFDPointer($0, $1)
235-
}
237+
_fixLifetime(self)
238+
_fixLifetime(suffix)
239+
return result
236240
}
237-
_fixLifetime(self)
238-
_fixLifetime(suffix)
239-
return result
240241
}
241-
return _stdlib_NSStringHasSuffixNFD(
242-
self._bridgeToObjectiveCImpl(),
243-
suffix._bridgeToObjectiveCImpl())
242+
243+
return self._ends(with: suffix)
244244
}
245245
}
246-
#else
247-
// FIXME: Implement hasPrefix and hasSuffix without objc
248-
// rdar://problem/18878343
249-
#endif
250246

251247
// Conversions to string from other types.
252248
extension String {

stdlib/public/core/Substring.swift.gyb

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -525,22 +525,6 @@ extension Substring.UnicodeScalarView : RangeReplaceableCollection {
525525
}
526526
}
527527

528-
#if _runtime(_ObjC)
529-
530-
extension Substring {
531-
@_inlineable // FIXME(sil-serialize-all)
532-
public func hasPrefix(_ prefix: String) -> Bool {
533-
return String(self).hasPrefix(prefix)
534-
}
535-
536-
@_inlineable // FIXME(sil-serialize-all)
537-
public func hasSuffix(_ suffix: String) -> Bool {
538-
return String(self).hasSuffix(suffix)
539-
}
540-
}
541-
542-
#endif
543-
544528
extension Substring : RangeReplaceableCollection {
545529
@_inlineable // FIXME(sil-serialize-all)
546530
public init<S : Sequence>(_ elements: S)

stdlib/public/stubs/SwiftNativeNSXXXBase.mm.gyb

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -144,55 +144,6 @@ swift_stdlib_CFStringHashCString(const uint8_t *bytes, CFIndex len) {
144144
return Result;
145145
}
146146

147-
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
148-
bool swift_stdlib_NSStringHasPrefixNFD(NSString *theString,
149-
NSString *prefix) {
150-
auto Length = CFStringGetLength((__bridge CFStringRef)theString);
151-
int Result = CFStringFindWithOptions(
152-
(__bridge CFStringRef)theString, (__bridge CFStringRef)prefix,
153-
CFRangeMake(0, Length), kCFCompareAnchored | kCFCompareNonliteral,
154-
nullptr);
155-
SWIFT_CC_PLUSONE_GUARD(swift_unknownRelease(theString));
156-
SWIFT_CC_PLUSONE_GUARD(swift_unknownRelease(prefix));
157-
return Result;
158-
}
159-
160-
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
161-
bool swift_stdlib_NSStringHasPrefixNFDPointer(void *theString,
162-
void *prefix) {
163-
auto Length = CFStringGetLength((__bridge CFStringRef)theString);
164-
int Result = CFStringFindWithOptions(
165-
(__bridge CFStringRef)theString, (__bridge CFStringRef)prefix,
166-
CFRangeMake(0, Length), kCFCompareAnchored | kCFCompareNonliteral,
167-
nullptr);
168-
return Result;
169-
}
170-
171-
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
172-
bool
173-
swift_stdlib_NSStringHasSuffixNFD(NSString *SWIFT_NS_RELEASES_ARGUMENT theString,
174-
NSString *SWIFT_NS_RELEASES_ARGUMENT suffix) {
175-
auto Length = CFStringGetLength((__bridge CFStringRef)theString);
176-
int Result = CFStringFindWithOptions(
177-
(__bridge CFStringRef)theString, (__bridge CFStringRef)suffix,
178-
CFRangeMake(0, Length),
179-
kCFCompareAnchored | kCFCompareBackwards | kCFCompareNonliteral, nullptr);
180-
SWIFT_CC_PLUSONE_GUARD(swift_unknownRelease(theString));
181-
SWIFT_CC_PLUSONE_GUARD(swift_unknownRelease(suffix));
182-
return Result;
183-
}
184-
185-
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
186-
bool swift_stdlib_NSStringHasSuffixNFDPointer(void *theString,
187-
void *suffix) {
188-
auto Length = CFStringGetLength((__bridge CFStringRef)theString);
189-
int Result = CFStringFindWithOptions(
190-
(__bridge CFStringRef)theString, (__bridge CFStringRef)suffix,
191-
CFRangeMake(0, Length),
192-
kCFCompareAnchored | kCFCompareBackwards | kCFCompareNonliteral, nullptr);
193-
return Result;
194-
}
195-
196147
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL
197148
void swift_stdlib_CFSetGetValues(NSSet *SWIFT_NS_RELEASES_ARGUMENT set,
198149
const void **values) {

test/stdlib/RuntimeObjC.swift

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -512,54 +512,6 @@ RuntimeFoundationWrappers.test("_stdlib_NSStringHashValuePointerNonASCII/NoLeak"
512512
expectEqual(0, nsStringCanaryCount)
513513
}
514514

515-
RuntimeFoundationWrappers.test("_stdlib_NSStringHasPrefixNFDPointer/NoLeak") {
516-
nsStringCanaryCount = 0
517-
autoreleasepool {
518-
let a = NSStringCanary()
519-
let b = NSStringCanary()
520-
expectEqual(2, nsStringCanaryCount)
521-
let ptrA = unsafeBitCast(a, to: OpaquePointer.self)
522-
let ptrB = unsafeBitCast(b, to: OpaquePointer.self)
523-
_stdlib_NSStringHasPrefixNFDPointer(ptrA, ptrB)
524-
}
525-
expectEqual(0, nsStringCanaryCount)
526-
}
527-
528-
RuntimeFoundationWrappers.test("_stdlib_NSStringHasSuffixNFDPointer/NoLeak") {
529-
nsStringCanaryCount = 0
530-
autoreleasepool {
531-
let a = NSStringCanary()
532-
let b = NSStringCanary()
533-
expectEqual(2, nsStringCanaryCount)
534-
let ptrA = unsafeBitCast(a, to: OpaquePointer.self)
535-
let ptrB = unsafeBitCast(b, to: OpaquePointer.self)
536-
_stdlib_NSStringHasSuffixNFDPointer(ptrA, ptrB)
537-
}
538-
expectEqual(0, nsStringCanaryCount)
539-
}
540-
541-
RuntimeFoundationWrappers.test("_stdlib_NSStringHasPrefixNFD/NoLeak") {
542-
nsStringCanaryCount = 0
543-
autoreleasepool {
544-
let a = NSStringCanary()
545-
let b = NSStringCanary()
546-
expectEqual(2, nsStringCanaryCount)
547-
_stdlib_NSStringHasPrefixNFD(a, b)
548-
}
549-
expectEqual(0, nsStringCanaryCount)
550-
}
551-
552-
RuntimeFoundationWrappers.test("_stdlib_NSStringHasSuffixNFD/NoLeak") {
553-
nsStringCanaryCount = 0
554-
autoreleasepool {
555-
let a = NSStringCanary()
556-
let b = NSStringCanary()
557-
expectEqual(2, nsStringCanaryCount)
558-
_stdlib_NSStringHasSuffixNFD(a, b)
559-
}
560-
expectEqual(0, nsStringCanaryCount)
561-
}
562-
563515
RuntimeFoundationWrappers.test("_stdlib_NSStringLowercaseString/NoLeak") {
564516
nsStringCanaryCount = 0
565517
autoreleasepool {

test/stdlib/StringAPI.swift

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -272,22 +272,11 @@ StringTests.test("LosslessStringConvertible") {
272272
let substringTests = tests.map {
273273
(test: ComparisonTest) -> ComparisonTest in
274274
switch (test.expectedUnicodeCollation, test.lhs, test.rhs) {
275-
case (.eq, "\u{0}", "\u{0}"):
276-
return test.replacingPredicate(.objCRuntime(
277-
"https://bugs.swift.org/browse/SR-332"))
278275

279276
case (.gt, "\r\n", "\n"):
280277
return test.replacingPredicate(.objCRuntime(
281278
"blocked on rdar://problem/19036555"))
282279

283-
case (.eq, "\u{0301}", "\u{0341}"):
284-
return test.replacingPredicate(.objCRuntime(
285-
"https://bugs.swift.org/browse/SR-243"))
286-
287-
case (.lt, "\u{1F1E7}", "\u{1F1E7}\u{1F1E7}"):
288-
return test.replacingPredicate(.objCRuntime(
289-
"https://bugs.swift.org/browse/SR-367"))
290-
291280
default:
292281
return test
293282
}

test/stdlib/StringCompatibility.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,6 @@ extension MyString : Hashable {
116116
}
117117
}
118118

119-
#if _runtime(_ObjC)
120-
121119
extension MyString {
122120
public func hasPrefix(_ prefix: String) -> Bool {
123121
return self.base.hasPrefix(prefix)
@@ -128,8 +126,6 @@ extension MyString {
128126
}
129127
}
130128

131-
#endif
132-
133129
extension MyString : StringProtocol {
134130
typealias UTF8Index = String.UTF8Index
135131
typealias UTF16Index = String.UTF16Index

0 commit comments

Comments
 (0)