Skip to content

Commit d174d92

Browse files
committed
string identical
1 parent 1d83fe3 commit d174d92

File tree

4 files changed

+367
-1
lines changed

4 files changed

+367
-1
lines changed

stdlib/public/core/String.swift

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

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

stdlib/public/core/Substring.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,3 +1307,43 @@ 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 between
1316+
/// 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 to
1321+
/// 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 && self._offsetRange == other._offsetRange
1328+
}
1329+
}
1330+
1331+
extension Substring {
1332+
/// Returns a boolean value indicating whether this substring is identical to
1333+
/// `other`.
1334+
///
1335+
/// Two substring values are identical if there is no way to distinguish between
1336+
/// them.
1337+
///
1338+
/// Comparing substrings this way includes comparing (normally) hidden
1339+
/// implementation details such as the memory location of any underlying
1340+
/// substring storage object. Therefore, identical substrings are guaranteed to
1341+
/// compare equal with `==`, but not all equal substrings are considered
1342+
/// identical.
1343+
///
1344+
/// - Performance: O(1)
1345+
@available(SwiftStdlib 6.3, *)
1346+
public func isIdentical(to other: Self) -> Bool {
1347+
self._isIdentical(to: other)
1348+
}
1349+
}

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()

test/stdlib/subString.swift

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,41 @@ func checkHasContiguousStorageSubstring(_ x: Substring.UTF8View) {
3131
expectTrue(hasStorage)
3232
}
3333

34+
fileprivate func slices(
35+
_ s: String,
36+
from: Int,
37+
to: Int
38+
) -> (
39+
Substring,
40+
Substring,
41+
Substring
42+
) {
43+
let s1 = s[s.index(s.startIndex, offsetBy: from) ..<
44+
s.index(s.startIndex, offsetBy: to)]
45+
let s2 = s1[s1.startIndex..<s1.endIndex]
46+
let s3 = s2[s1.startIndex..<s1.endIndex]
47+
return (s1, s2, s3)
48+
}
49+
50+
fileprivate func allNotEmpty(
51+
_ s: Substring...
52+
) -> Bool {
53+
s.allSatisfy { $0.isEmpty == false }
54+
}
55+
56+
fileprivate func allEqual(
57+
_ s: Substring...
58+
) -> Bool {
59+
for i in 0..<s.count {
60+
for j in (i + 1)..<s.count {
61+
if s[i] != s[j] {
62+
return false
63+
}
64+
}
65+
}
66+
return true
67+
}
68+
3469
SubstringTests.test("Equality") {
3570
let s = "abcdefg"
3671
let s1 = s[s.index(s.startIndex, offsetBy: 2) ..<
@@ -282,4 +317,219 @@ SubstringTests.test("Substring.base") {
282317
}
283318
}
284319

320+
SubstringTests.test("_isIdentical(to:) small ascii") {
321+
let (a1, a2, a3) = slices("Hello", from: 2, to: 4)
322+
let (b1, b2, b3) = slices("Hello", from: 2, to: 4)
323+
324+
precondition(allNotEmpty(a1, a2, a3, b1, b2, b3))
325+
precondition(allEqual(a1, a2, a3, b1, b2, b3))
326+
327+
expectTrue(a1._isIdentical(to: a1))
328+
expectTrue(a1._isIdentical(to: a2))
329+
expectTrue(a1._isIdentical(to: a3))
330+
expectTrue(a1._isIdentical(to: b1))
331+
expectTrue(a1._isIdentical(to: b2))
332+
expectTrue(a1._isIdentical(to: b3))
333+
334+
expectTrue(a2._isIdentical(to: a1))
335+
expectTrue(a2._isIdentical(to: a2))
336+
expectTrue(a2._isIdentical(to: a3))
337+
expectTrue(a2._isIdentical(to: b1))
338+
expectTrue(a2._isIdentical(to: b2))
339+
expectTrue(a2._isIdentical(to: b3))
340+
341+
expectTrue(a3._isIdentical(to: a1))
342+
expectTrue(a3._isIdentical(to: a2))
343+
expectTrue(a3._isIdentical(to: a3))
344+
expectTrue(a3._isIdentical(to: b1))
345+
expectTrue(a3._isIdentical(to: b2))
346+
expectTrue(a3._isIdentical(to: b3))
347+
}
348+
349+
SubstringTests.test("_isIdentical(to:) small unicode") {
350+
let (a1, a2, a3) = slices("Hello Cafe\u{301}", from: 2, to: 4)
351+
let (b1, b2, b3) = slices("Hello Cafe\u{301}", from: 2, to: 4)
352+
let (c1, c2, c3) = slices("Hello Café", from: 2, to: 4)
353+
354+
precondition(allNotEmpty(a1, a2, a3, b1, b2, b3, c1, c2, c3))
355+
precondition(allEqual(a1, a2, a3, b1, b2, b3, c1, c2, c3))
356+
357+
expectTrue(a1._isIdentical(to: a1))
358+
expectTrue(a1._isIdentical(to: a2))
359+
expectTrue(a1._isIdentical(to: a3))
360+
expectTrue(a1._isIdentical(to: b1))
361+
expectTrue(a1._isIdentical(to: b2))
362+
expectTrue(a1._isIdentical(to: b3))
363+
expectFalse(a1._isIdentical(to: c1))
364+
expectFalse(a1._isIdentical(to: c2))
365+
expectFalse(a1._isIdentical(to: c3))
366+
367+
expectTrue(a2._isIdentical(to: a1))
368+
expectTrue(a2._isIdentical(to: a2))
369+
expectTrue(a2._isIdentical(to: a3))
370+
expectTrue(a2._isIdentical(to: b1))
371+
expectTrue(a2._isIdentical(to: b2))
372+
expectTrue(a2._isIdentical(to: b3))
373+
expectFalse(a2._isIdentical(to: c1))
374+
expectFalse(a2._isIdentical(to: c2))
375+
expectFalse(a2._isIdentical(to: c3))
376+
377+
expectTrue(a3._isIdentical(to: a1))
378+
expectTrue(a3._isIdentical(to: a2))
379+
expectTrue(a3._isIdentical(to: a3))
380+
expectTrue(a3._isIdentical(to: b1))
381+
expectTrue(a3._isIdentical(to: b2))
382+
expectTrue(a3._isIdentical(to: b3))
383+
expectFalse(a3._isIdentical(to: c1))
384+
expectFalse(a3._isIdentical(to: c2))
385+
expectFalse(a3._isIdentical(to: c3))
386+
}
387+
388+
SubstringTests.test("_isIdentical(to:) large ascii") {
389+
let (a1, a2, a3) = slices(String(repeating: "Hello", count: 1000), from: 2, to: 4)
390+
let (b1, b2, b3) = slices(String(repeating: "Hello", count: 1000), from: 2, to: 4)
391+
392+
precondition(allNotEmpty(a1, a2, a3, b1, b2, b3))
393+
precondition(allEqual(a1, a2, a3, b1, b2, b3))
394+
395+
expectTrue(a1._isIdentical(to: a1))
396+
expectTrue(a1._isIdentical(to: a2))
397+
expectTrue(a1._isIdentical(to: a3))
398+
expectFalse(a1._isIdentical(to: b1))
399+
expectFalse(a1._isIdentical(to: b2))
400+
expectFalse(a1._isIdentical(to: b3))
401+
402+
expectTrue(a2._isIdentical(to: a1))
403+
expectTrue(a2._isIdentical(to: a2))
404+
expectTrue(a2._isIdentical(to: a3))
405+
expectFalse(a2._isIdentical(to: b1))
406+
expectFalse(a2._isIdentical(to: b2))
407+
expectFalse(a2._isIdentical(to: b3))
408+
409+
expectTrue(a3._isIdentical(to: a1))
410+
expectTrue(a3._isIdentical(to: a2))
411+
expectTrue(a3._isIdentical(to: a3))
412+
expectFalse(a3._isIdentical(to: b1))
413+
expectFalse(a3._isIdentical(to: b2))
414+
expectFalse(a3._isIdentical(to: b3))
415+
}
416+
417+
SubstringTests.test("isIdentical(to:) small ascii")
418+
.skip(.custom(
419+
{ if #available(SwiftStdlib 6.3, *) { false } else { true } },
420+
reason: "Requires Swift 6.3's standard library"
421+
))
422+
.code {
423+
guard #available(SwiftStdlib 6.3, *) else { return }
424+
425+
let (a1, a2, a3) = slices("Hello", from: 2, to: 4)
426+
let (b1, b2, b3) = slices("Hello", from: 2, to: 4)
427+
428+
precondition(allNotEmpty(a1, a2, a3, b1, b2, b3))
429+
precondition(allEqual(a1, a2, a3, b1, b2, b3))
430+
431+
expectTrue(a1.isIdentical(to: a1))
432+
expectTrue(a1.isIdentical(to: a2))
433+
expectTrue(a1.isIdentical(to: a3))
434+
expectTrue(a1.isIdentical(to: b1))
435+
expectTrue(a1.isIdentical(to: b2))
436+
expectTrue(a1.isIdentical(to: b3))
437+
438+
expectTrue(a2.isIdentical(to: a1))
439+
expectTrue(a2.isIdentical(to: a2))
440+
expectTrue(a2.isIdentical(to: a3))
441+
expectTrue(a2.isIdentical(to: b1))
442+
expectTrue(a2.isIdentical(to: b2))
443+
expectTrue(a2.isIdentical(to: b3))
444+
445+
expectTrue(a3.isIdentical(to: a1))
446+
expectTrue(a3.isIdentical(to: a2))
447+
expectTrue(a3.isIdentical(to: a3))
448+
expectTrue(a3.isIdentical(to: b1))
449+
expectTrue(a3.isIdentical(to: b2))
450+
expectTrue(a3.isIdentical(to: b3))
451+
}
452+
453+
SubstringTests.test("isIdentical(to:) small unicode")
454+
.skip(.custom(
455+
{ if #available(SwiftStdlib 6.3, *) { false } else { true } },
456+
reason: "Requires Swift 6.3's standard library"
457+
))
458+
.code {
459+
guard #available(SwiftStdlib 6.3, *) else { return }
460+
461+
let (a1, a2, a3) = slices("Hello Cafe\u{301}", from: 2, to: 4)
462+
let (b1, b2, b3) = slices("Hello Cafe\u{301}", from: 2, to: 4)
463+
let (c1, c2, c3) = slices("Hello Café", from: 2, to: 4)
464+
465+
precondition(allNotEmpty(a1, a2, a3, b1, b2, b3, c1, c2, c3))
466+
precondition(allEqual(a1, a2, a3, b1, b2, b3, c1, c2, c3))
467+
468+
expectTrue(a1.isIdentical(to: a1))
469+
expectTrue(a1.isIdentical(to: a2))
470+
expectTrue(a1.isIdentical(to: a3))
471+
expectTrue(a1.isIdentical(to: b1))
472+
expectTrue(a1.isIdentical(to: b2))
473+
expectTrue(a1.isIdentical(to: b3))
474+
expectFalse(a1.isIdentical(to: c1))
475+
expectFalse(a1.isIdentical(to: c2))
476+
expectFalse(a1.isIdentical(to: c3))
477+
478+
expectTrue(a2.isIdentical(to: a1))
479+
expectTrue(a2.isIdentical(to: a2))
480+
expectTrue(a2.isIdentical(to: a3))
481+
expectTrue(a2.isIdentical(to: b1))
482+
expectTrue(a2.isIdentical(to: b2))
483+
expectTrue(a2.isIdentical(to: b3))
484+
expectFalse(a2.isIdentical(to: c1))
485+
expectFalse(a2.isIdentical(to: c2))
486+
expectFalse(a2.isIdentical(to: c3))
487+
488+
expectTrue(a3.isIdentical(to: a1))
489+
expectTrue(a3.isIdentical(to: a2))
490+
expectTrue(a3.isIdentical(to: a3))
491+
expectTrue(a3.isIdentical(to: b1))
492+
expectTrue(a3.isIdentical(to: b2))
493+
expectTrue(a3.isIdentical(to: b3))
494+
expectFalse(a3.isIdentical(to: c1))
495+
expectFalse(a3.isIdentical(to: c2))
496+
expectFalse(a3.isIdentical(to: c3))
497+
}
498+
499+
SubstringTests.test("isIdentical(to:) large ascii")
500+
.skip(.custom(
501+
{ if #available(SwiftStdlib 6.3, *) { false } else { true } },
502+
reason: "Requires Swift 6.3's standard library"
503+
))
504+
.code {
505+
guard #available(SwiftStdlib 6.3, *) else { return }
506+
507+
let (a1, a2, a3) = slices(String(repeating: "Hello", count: 1000), from: 2, to: 4)
508+
let (b1, b2, b3) = slices(String(repeating: "Hello", count: 1000), from: 2, to: 4)
509+
510+
precondition(allNotEmpty(a1, a2, a3, b1, b2, b3))
511+
precondition(allEqual(a1, a2, a3, b1, b2, b3))
512+
513+
expectTrue(a1.isIdentical(to: a1))
514+
expectTrue(a1.isIdentical(to: a2))
515+
expectTrue(a1.isIdentical(to: a3))
516+
expectFalse(a1.isIdentical(to: b1))
517+
expectFalse(a1.isIdentical(to: b2))
518+
expectFalse(a1.isIdentical(to: b3))
519+
520+
expectTrue(a2.isIdentical(to: a1))
521+
expectTrue(a2.isIdentical(to: a2))
522+
expectTrue(a2.isIdentical(to: a3))
523+
expectFalse(a2.isIdentical(to: b1))
524+
expectFalse(a2.isIdentical(to: b2))
525+
expectFalse(a2.isIdentical(to: b3))
526+
527+
expectTrue(a3.isIdentical(to: a1))
528+
expectTrue(a3.isIdentical(to: a2))
529+
expectTrue(a3.isIdentical(to: a3))
530+
expectFalse(a3.isIdentical(to: b1))
531+
expectFalse(a3.isIdentical(to: b2))
532+
expectFalse(a3.isIdentical(to: b3))
533+
}
534+
285535
runAllTests()

0 commit comments

Comments
 (0)