Skip to content

Commit 8e5784c

Browse files
committed
Merge remote-tracking branch 'origin/main' into rebranch
2 parents bf812bb + 056b494 commit 8e5784c

File tree

2 files changed

+24
-20
lines changed

2 files changed

+24
-20
lines changed

stdlib/public/core/StringLegacy.swift

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,26 +124,34 @@ extension String {
124124
public func hasPrefix(_ prefix: String) -> Bool {
125125
if _fastPath(self._guts.isNFCFastUTF8 && prefix._guts.isNFCFastUTF8) {
126126
guard prefix._guts.count <= self._guts.count else { return false }
127-
return prefix._guts.withFastUTF8 { nfcPrefix in
127+
let isPrefix = prefix._guts.withFastUTF8 { nfcPrefix in
128128
let prefixEnd = nfcPrefix.count
129129
return self._guts.withFastUTF8(range: 0..<prefixEnd) { nfcSlicedSelf in
130130
return _binaryCompare(nfcSlicedSelf, nfcPrefix) == 0
131131
}
132132
}
133+
let endIndex = Index(_encodedOffset: prefix._guts.count)
134+
// In addition to a byte comparison check, we also need to check that
135+
// the prefix ends on a grapheme cluster boundary of the String
136+
return isPrefix && self._guts.isOnGraphemeClusterBoundary(endIndex)
133137
}
134138

135139
return starts(with: prefix)
136140
}
137141

138142
public func hasSuffix(_ suffix: String) -> Bool {
139143
if _fastPath(self._guts.isNFCFastUTF8 && suffix._guts.isNFCFastUTF8) {
140-
guard suffix._guts.count <= self._guts.count else { return false }
141-
return suffix._guts.withFastUTF8 { nfcSuffix in
142-
let suffixStart = self._guts.count - nfcSuffix.count
144+
let suffixStart = self._guts.count - suffix._guts.count
145+
guard suffixStart >= 0 else { return false }
146+
let isSuffix = suffix._guts.withFastUTF8 { nfcSuffix in
143147
return self._guts.withFastUTF8(range: suffixStart..<self._guts.count) {
144148
nfcSlicedSelf in return _binaryCompare(nfcSlicedSelf, nfcSuffix) == 0
145149
}
146150
}
151+
let startIndex = Index(_encodedOffset: suffixStart)
152+
// In addition to a byte comparison check, we also need to check that
153+
// the suffix starts on a grapheme cluster boundary of the String
154+
return isSuffix && self._guts.isOnGraphemeClusterBoundary(startIndex)
147155
}
148156

149157
return self.reversed().starts(with: suffix.reversed())

test/stdlib/StringAPI.swift

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -293,22 +293,7 @@ StringTests.test("LosslessStringConvertible") {
293293
checkLosslessStringConvertible(comparisonTests.map { $0.rhs })
294294
}
295295

296-
// Mark the test cases that are expected to fail in checkHasPrefixHasSuffix
297-
298-
let substringTests = tests.map {
299-
(test: ComparisonTest) -> ComparisonTest in
300-
switch (test.expectedUnicodeCollation, test.lhs, test.rhs) {
301-
302-
case (.gt, "\r\n", "\n"):
303-
return test.replacingPredicate(.objCRuntime(
304-
"blocked on rdar://problem/19036555"))
305-
306-
default:
307-
return test
308-
}
309-
}
310-
311-
for test in substringTests {
296+
for test in tests {
312297
StringTests.test("hasPrefix,hasSuffix: line \(test.loc.line)")
313298
.skip(.nativeRuntime(
314299
"String.has{Prefix,Suffix} defined when _runtime(_ObjC)"))
@@ -532,4 +517,15 @@ StringTests.test("_isIdentical(to:)") {
532517
expectTrue(g._isIdentical(to: g))
533518
}
534519

520+
StringTests.test("hasPrefix/hasSuffix vs Character boundaries") {
521+
// https://github.com/apple/swift/issues/67427
522+
let s1 = "\r\n"
523+
let s2 = "\r\n" + "cafe" + "\r\n"
524+
525+
expectFalse(s1.hasPrefix("\r"))
526+
expectFalse(s1.hasSuffix("\n"))
527+
expectFalse(s2.hasPrefix("\r"))
528+
expectFalse(s2.hasSuffix("\n"))
529+
}
530+
535531
runAllTests()

0 commit comments

Comments
 (0)