Skip to content

Commit 7462596

Browse files
committed
[stdlib] string identical
1 parent 9a0a831 commit 7462596

File tree

6 files changed

+427
-20
lines changed

6 files changed

+427
-20
lines changed

benchmark/single-source/StringTests.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ public var benchmarks: [BenchmarkInfo] {
4949
runFunction: run_iterateWords,
5050
tags: [.validation, .String]))
5151
}
52+
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999, *) {
53+
result.append(
54+
BenchmarkInfo(
55+
name: "StringIdentical",
56+
runFunction: run_StringIdentical,
57+
tags: [.validation, .String]))
58+
}
5259
return result
5360
}
5461

@@ -1676,3 +1683,14 @@ public func run_iterateWords(_ n: Int) {
16761683
blackHole(swiftOrgHTML._words)
16771684
}
16781685
}
1686+
1687+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999, *)
1688+
public func run_StringIdentical(_ n: Int) {
1689+
let str1 = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. "
1690+
let str2 = str1
1691+
for _ in 0 ..< n {
1692+
for _ in 0 ..< 100_000 {
1693+
check(str1.isIdentical(to: str2))
1694+
}
1695+
}
1696+
}

benchmark/single-source/SubstringTest.swift

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,36 @@
1212

1313
import TestsUtils
1414

15-
public let benchmarks = [
16-
BenchmarkInfo(name: "EqualStringSubstring", runFunction: run_EqualStringSubstring, tags: [.validation, .api, .String]),
17-
BenchmarkInfo(name: "EqualSubstringString", runFunction: run_EqualSubstringString, tags: [.validation, .api, .String]),
18-
BenchmarkInfo(name: "EqualSubstringSubstring", runFunction: run_EqualSubstringSubstring, tags: [.validation, .api, .String]),
19-
BenchmarkInfo(name: "EqualSubstringSubstringGenericEquatable", runFunction: run_EqualSubstringSubstringGenericEquatable, tags: [.validation, .api, .String]),
20-
BenchmarkInfo(name: "SubstringRemoveFirst1", runFunction: run_SubstringRemoveFirst1, tags: [.validation, .api, .String]),
21-
BenchmarkInfo(name: "SubstringRemoveLast1", runFunction: run_SubstringRemoveLast1, tags: [.validation, .api, .String]),
22-
BenchmarkInfo(name: "LessSubstringSubstring", runFunction: run_LessSubstringSubstring, tags: [.validation, .api, .String]),
23-
BenchmarkInfo(name: "LessSubstringSubstringGenericComparable", runFunction: run_LessSubstringSubstringGenericComparable, tags: [.validation, .api, .String]),
24-
BenchmarkInfo(name: "StringFromLongWholeSubstring", runFunction: run_StringFromLongWholeSubstring, tags: [.validation, .api, .String]),
25-
BenchmarkInfo(name: "StringFromLongWholeSubstringGeneric", runFunction: run_StringFromLongWholeSubstringGeneric, tags: [.validation, .api, .String]),
26-
BenchmarkInfo(name: "SubstringComparable", runFunction: run_SubstringComparable, tags: [.validation, .api, .String],
27-
setUpFunction: { blackHole(_comparison) }),
28-
BenchmarkInfo(name: "SubstringEqualString", runFunction: run_SubstringEqualString, tags: [.validation, .api, .String]),
29-
BenchmarkInfo(name: "SubstringEquatable", runFunction: run_SubstringEquatable, tags: [.validation, .api, .String]),
30-
BenchmarkInfo(name: "SubstringFromLongString2", runFunction: run_SubstringFromLongString, tags: [.validation, .api, .String]),
31-
BenchmarkInfo(name: "SubstringFromLongStringGeneric2", runFunction: run_SubstringFromLongStringGeneric, tags: [.validation, .api, .String]),
32-
BenchmarkInfo(name: "SubstringTrimmingASCIIWhitespace", runFunction: run_SubstringTrimmingASCIIWhitespace, tags: [.validation, .api, .String]),
33-
]
15+
public var benchmarks: [BenchmarkInfo] {
16+
var result = [
17+
BenchmarkInfo(name: "EqualStringSubstring", runFunction: run_EqualStringSubstring, tags: [.validation, .api, .String]),
18+
BenchmarkInfo(name: "EqualSubstringString", runFunction: run_EqualSubstringString, tags: [.validation, .api, .String]),
19+
BenchmarkInfo(name: "EqualSubstringSubstring", runFunction: run_EqualSubstringSubstring, tags: [.validation, .api, .String]),
20+
BenchmarkInfo(name: "EqualSubstringSubstringGenericEquatable", runFunction: run_EqualSubstringSubstringGenericEquatable, tags: [.validation, .api, .String]),
21+
BenchmarkInfo(name: "SubstringRemoveFirst1", runFunction: run_SubstringRemoveFirst1, tags: [.validation, .api, .String]),
22+
BenchmarkInfo(name: "SubstringRemoveLast1", runFunction: run_SubstringRemoveLast1, tags: [.validation, .api, .String]),
23+
BenchmarkInfo(name: "LessSubstringSubstring", runFunction: run_LessSubstringSubstring, tags: [.validation, .api, .String]),
24+
BenchmarkInfo(name: "LessSubstringSubstringGenericComparable", runFunction: run_LessSubstringSubstringGenericComparable, tags: [.validation, .api, .String]),
25+
BenchmarkInfo(name: "StringFromLongWholeSubstring", runFunction: run_StringFromLongWholeSubstring, tags: [.validation, .api, .String]),
26+
BenchmarkInfo(name: "StringFromLongWholeSubstringGeneric", runFunction: run_StringFromLongWholeSubstringGeneric, tags: [.validation, .api, .String]),
27+
BenchmarkInfo(name: "SubstringComparable", runFunction: run_SubstringComparable, tags: [.validation, .api, .String],
28+
setUpFunction: { blackHole(_comparison) }),
29+
BenchmarkInfo(name: "SubstringEqualString", runFunction: run_SubstringEqualString, tags: [.validation, .api, .String]),
30+
BenchmarkInfo(name: "SubstringEquatable", runFunction: run_SubstringEquatable, tags: [.validation, .api, .String]),
31+
BenchmarkInfo(name: "SubstringFromLongString2", runFunction: run_SubstringFromLongString, tags: [.validation, .api, .String]),
32+
BenchmarkInfo(name: "SubstringFromLongStringGeneric2", runFunction: run_SubstringFromLongStringGeneric, tags: [.validation, .api, .String]),
33+
BenchmarkInfo(name: "SubstringTrimmingASCIIWhitespace", runFunction: run_SubstringTrimmingASCIIWhitespace, tags: [.validation, .api, .String]),
34+
]
35+
36+
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999, *) {
37+
result.append(
38+
BenchmarkInfo(
39+
name: "SubstringIdentical",
40+
runFunction: run_SubstringIdentical,
41+
tags: [.validation, .String]))
42+
}
43+
return result
44+
}
3445

3546
// A string that doesn't fit in small string storage and doesn't fit in Latin-1
3647
let longWide = "fὢasὢodὢijὢadὢolὢsjὢalὢsdὢjlὢasὢdfὢijὢliὢsdὢjøὢslὢdiὢalὢiὢ"
@@ -332,3 +343,12 @@ public func run _LessSubstringSubstringGenericStringProtocol(_ n: Int) {
332343
}
333344
}
334345
*/
346+
347+
@inline(never)
348+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999, *)
349+
public func run_SubstringIdentical(_ n: Int) {
350+
let (a, b) = (ss1, ss1)
351+
for _ in 1...n*500 {
352+
blackHole(a.isIdentical(to: b))
353+
}
354+
}

stdlib/public/core/String.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1112,4 +1112,23 @@ extension String {
11121112
}
11131113
}
11141114

1115-
1115+
@available(SwiftStdlib 6.3, *)
1116+
extension String {
1117+
/// Returns a boolean value indicating whether this string is identical to
1118+
/// `other`.
1119+
///
1120+
/// Two string values are identical if there is no way to distinguish between
1121+
/// them.
1122+
///
1123+
/// Comparing strings this way includes comparing (normally) hidden
1124+
/// implementation details such as the memory location of any underlying
1125+
/// string storage object. Therefore, identical strings are guaranteed to
1126+
/// compare equal with `==`, but not all equal strings are considered
1127+
/// identical.
1128+
///
1129+
/// - Performance: O(1)
1130+
@available(SwiftStdlib 6.3, *)
1131+
public func isIdentical(to other: Self) -> Bool {
1132+
self._isIdentical(to: other)
1133+
}
1134+
}

stdlib/public/core/Substring.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,3 +1307,45 @@ extension Substring {
13071307
return Substring(_unchecked: Slice(base: base, bounds: r))
13081308
}
13091309
}
1310+
1311+
extension Substring {
1312+
/// Returns a boolean value indicating whether this substring is identical to
1313+
/// `other`.
1314+
///
1315+
/// Two substring values are identical if there is no way to distinguish
1316+
/// between them.
1317+
///
1318+
/// Comparing substrings this way includes comparing (normally) hidden
1319+
/// implementation details such as the memory location of any underlying
1320+
/// substring storage object. Therefore, identical substrings are guaranteed
1321+
/// to compare equal with `==`, but not all equal substrings are considered
1322+
/// identical.
1323+
///
1324+
/// - Performance: O(1)
1325+
@_alwaysEmitIntoClient
1326+
public func _isIdentical(to other: Self) -> Bool {
1327+
self._wholeGuts.rawBits == other._wholeGuts.rawBits &&
1328+
self._offsetRange == other._offsetRange
1329+
}
1330+
}
1331+
1332+
@available(SwiftStdlib 6.3, *)
1333+
extension Substring {
1334+
/// Returns a boolean value indicating whether this substring is identical to
1335+
/// `other`.
1336+
///
1337+
/// Two substring values are identical if there is no way to distinguish
1338+
/// between them.
1339+
///
1340+
/// Comparing substrings this way includes comparing (normally) hidden
1341+
/// implementation details such as the memory location of any underlying
1342+
/// substring storage object. Therefore, identical substrings are guaranteed
1343+
/// to compare equal with `==`, but not all equal substrings are considered
1344+
/// identical.
1345+
///
1346+
/// - Performance: O(1)
1347+
@available(SwiftStdlib 6.3, *)
1348+
public func isIdentical(to other: Self) -> Bool {
1349+
self._isIdentical(to: other)
1350+
}
1351+
}

test/stdlib/StringAPI.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,4 +533,62 @@ StringTests.test("hasPrefix/hasSuffix vs Character boundaries") {
533533
expectFalse(s2.hasSuffix("\n"))
534534
}
535535

536+
StringTests.test("isIdentical(to:) small ascii")
537+
.skip(.custom(
538+
{ if #available(SwiftStdlib 6.3, *) { false } else { true } },
539+
reason: "Requires Swift 6.3's standard library"
540+
))
541+
.code {
542+
guard #available(SwiftStdlib 6.3, *) else { return }
543+
544+
let a = "Hello"
545+
let b = "Hello"
546+
547+
precondition(a == b)
548+
549+
expectTrue(a.isIdentical(to: a))
550+
expectTrue(b.isIdentical(to: b))
551+
expectTrue(a.isIdentical(to: b)) // Both small ASCII strings
552+
expectTrue(b.isIdentical(to: a))
553+
}
554+
555+
StringTests.test("isIdentical(to:) small unicode")
556+
.skip(.custom(
557+
{ if #available(SwiftStdlib 6.3, *) { false } else { true } },
558+
reason: "Requires Swift 6.3's standard library"
559+
))
560+
.code {
561+
guard #available(SwiftStdlib 6.3, *) else { return }
562+
563+
let a = "Cafe\u{301}"
564+
let b = "Cafe\u{301}"
565+
let c = "Café"
566+
567+
precondition(a == b)
568+
precondition(b == c)
569+
570+
expectTrue(a.isIdentical(to: b))
571+
expectTrue(b.isIdentical(to: a))
572+
expectFalse(a.isIdentical(to: c))
573+
expectFalse(b.isIdentical(to: c))
574+
}
575+
576+
StringTests.test("isIdentical(to:) large ascii")
577+
.skip(.custom(
578+
{ if #available(SwiftStdlib 6.3, *) { false } else { true } },
579+
reason: "Requires Swift 6.3's standard library"
580+
))
581+
.code {
582+
guard #available(SwiftStdlib 6.3, *) else { return }
583+
584+
let a = String(repeating: "foo", count: 1000)
585+
let b = String(repeating: "foo", count: 1000)
586+
587+
precondition(a == b)
588+
589+
expectFalse(a.isIdentical(to: b)) // Two large, distinct native strings
590+
expectTrue(a.isIdentical(to: a))
591+
expectTrue(b.isIdentical(to: b))
592+
}
593+
536594
runAllTests()

0 commit comments

Comments
 (0)