diff --git a/benchmark/single-source/StringTests.swift b/benchmark/single-source/StringTests.swift index bcc43a2633777..d3890e241cb48 100644 --- a/benchmark/single-source/StringTests.swift +++ b/benchmark/single-source/StringTests.swift @@ -14,7 +14,7 @@ import TestsUtils @_spi(_Unicode) import Swift -public var benchmarks: [BenchmarkInfo] { +public let benchmarks: [BenchmarkInfo] = { var result = [ BenchmarkInfo( name: "StringEqualPointerComparison", @@ -50,7 +50,7 @@ public var benchmarks: [BenchmarkInfo] { tags: [.validation, .String])) } return result -} +}() // FIXME(string) public func run_StringHasPrefixAscii(_ n: Int) { diff --git a/include/swift/AST/RuntimeVersions.def b/include/swift/AST/RuntimeVersions.def index 39d0a279c6f28..4ab18020ff311 100644 --- a/include/swift/AST/RuntimeVersions.def +++ b/include/swift/AST/RuntimeVersions.def @@ -171,6 +171,11 @@ RUNTIME_VERSION( FUTURE ) +RUNTIME_VERSION( + (6, 4), + FUTURE +) + END_MAJOR_VERSION(6) // .......................................................................... // diff --git a/stdlib/public/core/Availability.swift b/stdlib/public/core/Availability.swift index a8fbce3e06e37..1c321827456bc 100644 --- a/stdlib/public/core/Availability.swift +++ b/stdlib/public/core/Availability.swift @@ -258,8 +258,10 @@ extension _SwiftStdlibVersion { public static var v6_2_0: Self { Self(_value: 0x060200) } @_alwaysEmitIntoClient public static var v6_3_0: Self { Self(_value: 0x060300) } + @_alwaysEmitIntoClient + public static var v6_4_0: Self { Self(_value: 0x060400) } - private static var _current: Self { .v6_3_0 } + private static var _current: Self { .v6_4_0 } #if hasFeature(Macros) @available(SwiftStdlib 5.7, *) diff --git a/stdlib/public/core/String.swift b/stdlib/public/core/String.swift index e6715e91cc6ed..7bf955d79e0e7 100644 --- a/stdlib/public/core/String.swift +++ b/stdlib/public/core/String.swift @@ -1112,4 +1112,41 @@ extension String { } } - +extension String { + /// Returns a boolean value indicating whether this string is identical to + /// `other`. + /// + /// Two string values are identical if there is no way to distinguish between + /// them. + /// + /// For any values `a`, `b`, and `c`: + /// + /// - `a.isTriviallyIdentical(to: a)` is always `true`. (Reflexivity) + /// - `a.isTriviallyIdentical(to: b)` implies `b.isTriviallyIdentical(to: a)`. + /// (Symmetry) + /// - If `a.isTriviallyIdentical(to: b)` and `b.isTriviallyIdentical(to: c)` + /// are both `true`, then `a.isTriviallyIdentical(to: c)` is also `true`. + /// (Transitivity) + /// - `a.isTriviallyIdentical(b)` implies `a == b`. `a == b` does not imply `a.isTriviallyIdentical(b)` + /// + /// Values produced by copying the same value, with no intervening mutations, + /// will compare identical: + /// + /// ```swift + /// let d = c + /// print(c.isTriviallyIdentical(to: d)) + /// // Prints true + /// ``` + /// + /// Comparing strings this way includes comparing (normally) hidden + /// implementation details such as the memory location of any underlying + /// string storage object. Therefore, identical strings are guaranteed to + /// compare equal with `==`, but not all equal strings are considered + /// identical. + /// + /// - Complexity: O(1) + @available(StdlibDeploymentTarget 6.4, *) + public func isTriviallyIdentical(to other: Self) -> Bool { + self._guts.isTriviallyIdentical(to: other._guts) + } +} diff --git a/stdlib/public/core/StringGuts.swift b/stdlib/public/core/StringGuts.swift index 4ae5123bc2f44..fb89a21b57a62 100644 --- a/stdlib/public/core/StringGuts.swift +++ b/stdlib/public/core/StringGuts.swift @@ -418,6 +418,13 @@ extension _StringGuts { } } +extension _StringGuts { + @inline(__always) // Performance + internal func isTriviallyIdentical(to other: Self) -> Bool { + self.rawBits == other.rawBits + } +} + #if _runtime(_ObjC) extension _StringGuts { diff --git a/stdlib/public/core/StringUTF16View.swift b/stdlib/public/core/StringUTF16View.swift index f4811c3ac98ba..bae33dd151ddd 100644 --- a/stdlib/public/core/StringUTF16View.swift +++ b/stdlib/public/core/StringUTF16View.swift @@ -1056,3 +1056,14 @@ extension String.UTF16View { } } } + +extension String.UTF16View { + /// Returns a boolean value indicating whether this UTF16 view + /// is trivially identical to `other`. + /// + /// - Complexity: O(1) + @available(StdlibDeploymentTarget 6.4, *) + public func isTriviallyIdentical(to other: Self) -> Bool { + self._guts.isTriviallyIdentical(to: other._guts) + } +} diff --git a/stdlib/public/core/StringUTF8View.swift b/stdlib/public/core/StringUTF8View.swift index 20f97379c460b..12b239843a6c5 100644 --- a/stdlib/public/core/StringUTF8View.swift +++ b/stdlib/public/core/StringUTF8View.swift @@ -684,3 +684,14 @@ extension String.UTF8View { return unsafe try _guts.withFastUTF8(body) } } + +extension String.UTF8View { + /// Returns a boolean value indicating whether this UTF8 view + /// is trivially identical to `other`. + /// + /// - Complexity: O(1) + @available(StdlibDeploymentTarget 6.4, *) + public func isTriviallyIdentical(to other: Self) -> Bool { + self._guts.isTriviallyIdentical(to: other._guts) + } +} diff --git a/stdlib/public/core/StringUnicodeScalarView.swift b/stdlib/public/core/StringUnicodeScalarView.swift index 271cd9b1ece77..122f4a524f0a0 100644 --- a/stdlib/public/core/StringUnicodeScalarView.swift +++ b/stdlib/public/core/StringUnicodeScalarView.swift @@ -527,3 +527,14 @@ extension String.UnicodeScalarView { return r._knownUTF16 } } + +extension String.UnicodeScalarView { + /// Returns a boolean value indicating whether this unicode scalar view + /// is trivially identical to `other`. + /// + /// - Complexity: O(1) + @available(StdlibDeploymentTarget 6.4, *) + public func isTriviallyIdentical(to other: Self) -> Bool { + self._guts.isTriviallyIdentical(to: other._guts) + } +} diff --git a/stdlib/public/core/Substring.swift b/stdlib/public/core/Substring.swift index e4e91b6a02a4c..c117c7110f7c2 100644 --- a/stdlib/public/core/Substring.swift +++ b/stdlib/public/core/Substring.swift @@ -882,6 +882,18 @@ extension Substring.UTF8View { #endif // !(os(watchOS) && _pointerBitWidth(_32)) } +extension Substring.UTF8View { + /// Returns a boolean value indicating whether this UTF8 view + /// is trivially identical to `other`. + /// + /// - Complexity: O(1) + @available(StdlibDeploymentTarget 6.4, *) + public func isTriviallyIdentical(to other: Self) -> Bool { + self._base.isTriviallyIdentical(to: other._base) + && self._bounds == other._bounds + } +} + extension Substring { @inlinable public var utf8: UTF8View { @@ -1037,6 +1049,18 @@ extension Substring.UTF16View: BidirectionalCollection { } } +extension Substring.UTF16View { + /// Returns a boolean value indicating whether this UTF16 view + /// is trivially identical to `other`. + /// + /// - Complexity: O(1) + @available(StdlibDeploymentTarget 6.4, *) + public func isTriviallyIdentical(to other: Self) -> Bool { + self._base.isTriviallyIdentical(to: other._base) + && self._bounds == other._bounds + } +} + extension Substring { @inlinable public var utf16: UTF16View { @@ -1281,6 +1305,18 @@ extension Substring.UnicodeScalarView: RangeReplaceableCollection { } } +extension Substring.UnicodeScalarView { + /// Returns a boolean value indicating whether this unicode scalar view + /// is trivially identical to `other`. + /// + /// - Complexity: O(1) + @available(StdlibDeploymentTarget 6.4, *) + public func isTriviallyIdentical(to other: Self) -> Bool { + self._slice._base.isTriviallyIdentical(to: other._slice._base) + && self._bounds == other._bounds + } +} + extension Substring: RangeReplaceableCollection { @_specialize(where S == String) @_specialize(where S == Substring) @@ -1385,3 +1421,43 @@ extension Substring { return Substring(_unchecked: Slice(base: base, bounds: r)) } } + +extension Substring { + /// Returns a boolean value indicating whether this substring is identical to + /// `other`. + /// + /// Two substring values are identical if there is no way to distinguish + /// between them. + /// + /// For any values `a`, `b`, and `c`: + /// + /// - `a.isTriviallyIdentical(to: a)` is always `true`. (Reflexivity) + /// - `a.isTriviallyIdentical(to: b)` implies `b.isTriviallyIdentical(to: a)`. + /// (Symmetry) + /// - If `a.isTriviallyIdentical(to: b)` and `b.isTriviallyIdentical(to: c)` + /// are both `true`, then `a.isTriviallyIdentical(to: c)` is also `true`. + /// (Transitivity) + /// - `a.isTriviallyIdentical(b)` implies `a == b`. `a == b` does not imply `a.isTriviallyIdentical(b)` + /// + /// Values produced by copying the same value, with no intervening mutations, + /// will compare identical: + /// + /// ```swift + /// let d = c + /// print(c.isTriviallyIdentical(to: d)) + /// // Prints true + /// ``` + /// + /// Comparing substrings this way includes comparing (normally) hidden + /// implementation details such as the memory location of any underlying + /// substring storage object. Therefore, identical substrings are guaranteed + /// to compare equal with `==`, but not all equal substrings are considered + /// identical. + /// + /// - Complexity: O(1) + @available(StdlibDeploymentTarget 6.4, *) + public func isTriviallyIdentical(to other: Self) -> Bool { + self._wholeGuts.isTriviallyIdentical(to: other._wholeGuts) && + self._offsetRange == other._offsetRange + } +} diff --git a/test/Interop/SwiftToCxx/stdlib/swift-stdlib-in-cxx.swift b/test/Interop/SwiftToCxx/stdlib/swift-stdlib-in-cxx.swift index e3455dfdf2efb..0812d31515b7b 100644 --- a/test/Interop/SwiftToCxx/stdlib/swift-stdlib-in-cxx.swift +++ b/test/Interop/SwiftToCxx/stdlib/swift-stdlib-in-cxx.swift @@ -124,6 +124,7 @@ // CHECK: SWIFT_INLINE_THUNK uint8_t operator [](const __StringNested::Index& i) const SWIFT_SYMBOL({{.*}}); // CHECK: SWIFT_INLINE_THUNK String getDescription() const SWIFT_SYMBOL({{.*}}); // CHECK: SWIFT_INLINE_THUNK swift::Int getCount() const SWIFT_SYMBOL({{.*}}); +// CHECK: SWIFT_INLINE_THUNK bool isTriviallyIdenticalTo(const __StringNested::UTF8View& other) const SWIFT_SYMBOL({{.*}}) {{.*}}; // CHECK-NEXT: private: // CHECK: class AnyKeyPath { } SWIFT_UNAVAILABLE_MSG("class 'AnyKeyPath' is not yet exposed to C++"); diff --git a/test/abi/macOS/arm64/stdlib.swift b/test/abi/macOS/arm64/stdlib.swift index d299100e8e000..8c4e44927e5b4 100644 --- a/test/abi/macOS/arm64/stdlib.swift +++ b/test/abi/macOS/arm64/stdlib.swift @@ -771,6 +771,7 @@ Added: _$ss7UnicodeO5UTF32O27encodedReplacementCharacters15CollectionOfOneVys6UI Added: _$sSo19_SwiftStdlibVersionasE6v6_1_0ABvpZMV Added: _$sSo19_SwiftStdlibVersionasE6v6_2_0ABvpZMV Added: _$sSo19_SwiftStdlibVersionasE6v6_3_0ABvpZMV +Added: _$sSo19_SwiftStdlibVersionasE6v6_4_0ABvpZMV Added: _$sSBsE5radixSivpZMV Added: _$sSFsE8ulpOfOnexvpZMV Added: _$sSUsE8isSignedSbvpZMV @@ -1161,3 +1162,13 @@ Added: _$ss13EncodingErrorO16debugDescriptionSSvg Added: _$ss13EncodingErrorO16debugDescriptionSSvpMV Added: _$ss13EncodingErrorOs28CustomDebugStringConvertiblesMc Added: _$ss13EncodingErrorOs28CustomDebugStringConvertiblesWP + +// SE-0494 Add isTriviallyIdentical(to:) Methods to String and Substring +Added: _$sSS20isTriviallyIdentical2toSbSS_tF +Added: _$sSs20isTriviallyIdentical2toSbSs_tF +Added: _$sSS17UnicodeScalarViewV20isTriviallyIdentical2toSbAB_tF +Added: _$sSS8UTF8ViewV20isTriviallyIdentical2toSbAB_tF +Added: _$sSS9UTF16ViewV20isTriviallyIdentical2toSbAB_tF +Added: _$sSs17UnicodeScalarViewV20isTriviallyIdentical2toSbAB_tF +Added: _$sSs8UTF8ViewV20isTriviallyIdentical2toSbAB_tF +Added: _$sSs9UTF16ViewV20isTriviallyIdentical2toSbAB_tF diff --git a/test/abi/macOS/x86_64/stdlib.swift b/test/abi/macOS/x86_64/stdlib.swift index a75fc1bd26ac0..766f0924a606f 100644 --- a/test/abi/macOS/x86_64/stdlib.swift +++ b/test/abi/macOS/x86_64/stdlib.swift @@ -752,6 +752,7 @@ Added: _$ss8SIMDMaskVss6SIMD64Vys5Int64VGRszrlE7allTrueAByAGGvpZMV Added: _$sSo19_SwiftStdlibVersionasE6v6_1_0ABvpZMV Added: _$sSo19_SwiftStdlibVersionasE6v6_2_0ABvpZMV Added: _$sSo19_SwiftStdlibVersionasE6v6_3_0ABvpZMV +Added: _$sSo19_SwiftStdlibVersionasE6v6_4_0ABvpZMV Added: _$ss7Float80V12signalingNaNABvpZMV Added: _$ss7Float80V13_exponentBiasSuvpZMV Added: _$ss7Float80V13_quietNaNMasks6UInt64VvpZMV @@ -1156,3 +1157,13 @@ Added: _$ss13EncodingErrorO16debugDescriptionSSvg Added: _$ss13EncodingErrorO16debugDescriptionSSvpMV Added: _$ss13EncodingErrorOs28CustomDebugStringConvertiblesMc Added: _$ss13EncodingErrorOs28CustomDebugStringConvertiblesWP + +// SE-0494 Add isTriviallyIdentical(to:) Methods to String and Substring +Added: _$sSS20isTriviallyIdentical2toSbSS_tF +Added: _$sSs20isTriviallyIdentical2toSbSs_tF +Added: _$sSS17UnicodeScalarViewV20isTriviallyIdentical2toSbAB_tF +Added: _$sSS8UTF8ViewV20isTriviallyIdentical2toSbAB_tF +Added: _$sSS9UTF16ViewV20isTriviallyIdentical2toSbAB_tF +Added: _$sSs17UnicodeScalarViewV20isTriviallyIdentical2toSbAB_tF +Added: _$sSs8UTF8ViewV20isTriviallyIdentical2toSbAB_tF +Added: _$sSs9UTF16ViewV20isTriviallyIdentical2toSbAB_tF diff --git a/test/stdlib/StringAPI.swift b/test/stdlib/StringAPI.swift index fb33db9b53ee7..835cf70758b78 100644 --- a/test/stdlib/StringAPI.swift +++ b/test/stdlib/StringAPI.swift @@ -522,6 +522,130 @@ StringTests.test("_isIdentical(to:)") { expectTrue(g._isIdentical(to: g)) } +StringTests.test("String.UnicodeScalarView.isTriviallyIdentical(to:)") + .skip(.custom({ + if #available(StdlibDeploymentTarget 6.4, *) { false } else { true } + }, reason: "Requires Swift stdlib 6.4")) + .code { + guard #available(StdlibDeploymentTarget 6.4, *) else { return } + + let a = "Hello".unicodeScalars + let b = "Hello".unicodeScalars + expectTrue(a.isTriviallyIdentical(to: a)) + expectTrue(b.isTriviallyIdentical(to: b)) + expectTrue(a.isTriviallyIdentical(to: b)) + expectTrue(b.isTriviallyIdentical(to: a)) + + let c = "Abcde".unicodeScalars + expectFalse(a.isTriviallyIdentical(to: c)) + expectFalse(c.isTriviallyIdentical(to: a)) + } +StringTests.test("String.UTF8View.isTriviallyIdentical(to:)") + .skip(.custom({ + if #available(StdlibDeploymentTarget 6.4, *) { false } else { true } + }, reason: "Requires Swift stdlib 6.4")) + .code { + guard #available(StdlibDeploymentTarget 6.4, *) else { return } + + let a = "Hello".utf8 + let b = "Hello".utf8 + expectTrue(a.isTriviallyIdentical(to: a)) + expectTrue(b.isTriviallyIdentical(to: b)) + expectTrue(a.isTriviallyIdentical(to: b)) + expectTrue(b.isTriviallyIdentical(to: a)) + + + let c = "Abcde".utf8 + expectFalse(a.isTriviallyIdentical(to: c)) + expectFalse(c.isTriviallyIdentical(to: a)) + } +StringTests.test("String.UTF16View.isTriviallyIdentical(to:)") + .skip(.custom({ + if #available(StdlibDeploymentTarget 6.4, *) { false } else { true } + }, reason: "Requires Swift stdlib 6.4")) + .code { + guard #available(StdlibDeploymentTarget 6.4, *) else { return } + + let a = "Hello".utf16 + let b = "Hello".utf16 + expectTrue(a.isTriviallyIdentical(to: a)) + expectTrue(b.isTriviallyIdentical(to: b)) + expectTrue(a.isTriviallyIdentical(to: b)) + expectTrue(b.isTriviallyIdentical(to: a)) + + + let c = "Abcde".utf16 + expectFalse(a.isTriviallyIdentical(to: c)) + expectFalse(c.isTriviallyIdentical(to: a)) + } + +StringTests.test("Substring.UnicodeScalarView.isTriviallyIdentical(to:)") + .skip(.custom({ + if #available(StdlibDeploymentTarget 6.4, *) { false } else { true } + }, reason: "Requires Swift stdlib 6.4")) + .code { + guard #available(StdlibDeploymentTarget 6.4, *) else { return } + + let base1 = "Test String" + let a = base1[base1.index(base1.startIndex, offsetBy: 5).. ( + Substring, + Substring, + Substring +) { + let s1 = s[s.index(s.startIndex, offsetBy: from) ..< + s.index(s.startIndex, offsetBy: to)] + let s2 = s1[s1.startIndex.. Bool { + s.allSatisfy { $0.isEmpty == false } +} + +fileprivate func allEqual( + _ s: Substring... +) -> Bool { + for i in 0..