diff --git a/stdlib/public/core/Mirror.swift b/stdlib/public/core/Mirror.swift index 960652c6546b1..75c210feacc91 100644 --- a/stdlib/public/core/Mirror.swift +++ b/stdlib/public/core/Mirror.swift @@ -590,6 +590,9 @@ extension String { // also preserve source compatibility for clients which accidentally // used init(stringInterpolationSegment:) through constructs like // myArray.map(String.init). + // + // Unfortunately they create ambiguity by being parallel to each + // other, so many overloads are required. /// Creates a string representing the given value. /// @@ -631,10 +634,100 @@ extension String { /// print(String(describing: p)) /// // Prints "(21, 30)" @inlinable + @_alwaysEmitIntoClient + public init(describing instance: borrowing Subject) { + self = instance.description + } + + /// Creates a string representing the given value. + /// + /// Use this initializer to convert an instance of any type to its preferred + /// representation as a `String` instance. The initializer creates the + /// string representation of `instance` in one of the following ways, + /// depending on its protocol conformance: + /// + /// - If `instance` conforms to the `TextOutputStreamable` protocol, the + /// result is obtained by calling `instance.write(to: s)` on an empty + /// string `s`. + /// - If `instance` conforms to the `CustomStringConvertible` protocol, the + /// result is `instance.description`. + /// - If `instance` conforms to the `CustomDebugStringConvertible` protocol, + /// the result is `instance.debugDescription`. + /// - An unspecified result is supplied automatically by the Swift standard + /// library. + /// + /// For example, this custom `Point` struct uses the default representation + /// supplied by the standard library. + /// + /// struct Point { + /// let x: Int, y: Int + /// } + /// + /// let p = Point(x: 21, y: 30) + /// print(String(describing: p)) + /// // Prints "Point(x: 21, y: 30)" + /// + /// After adding `CustomStringConvertible` conformance by implementing the + /// `description` property, `Point` provides its own custom representation. + /// + /// extension Point: CustomStringConvertible { + /// var description: String { + /// return "(\(x), \(y))" + /// } + /// } + /// + /// print(String(describing: p)) + /// // Prints "(21, 30)" public init(describing instance: Subject) { self = instance.description } + /// Creates a string representing the given value. + /// + /// Use this initializer to convert an instance of any type to its preferred + /// representation as a `String` instance. The initializer creates the + /// string representation of `instance` in one of the following ways, + /// depending on its protocol conformance: + /// + /// - If `instance` conforms to the `TextOutputStreamable` protocol, the + /// result is obtained by calling `instance.write(to: s)` on an empty + /// string `s`. + /// - If `instance` conforms to the `CustomStringConvertible` protocol, the + /// result is `instance.description`. + /// - If `instance` conforms to the `CustomDebugStringConvertible` protocol, + /// the result is `instance.debugDescription`. + /// - An unspecified result is supplied automatically by the Swift standard + /// library. + /// + /// For example, this custom `Point` struct uses the default representation + /// supplied by the standard library. + /// + /// struct Point { + /// let x: Int, y: Int + /// } + /// + /// let p = Point(x: 21, y: 30) + /// print(String(describing: p)) + /// // Prints "Point(x: 21, y: 30)" + /// + /// After adding `CustomStringConvertible` conformance by implementing the + /// `description` property, `Point` provides its own custom representation. + /// + /// extension Point: CustomStringConvertible { + /// var description: String { + /// return "(\(x), \(y))" + /// } + /// } + /// + /// print(String(describing: p)) + /// // Prints "(21, 30)" + @inlinable + @_alwaysEmitIntoClient + public init(describing instance: borrowing Subject) { + self.init() + instance.write(to: &self) + } + /// Creates a string representing the given value. /// /// Use this initializer to convert an instance of any type to its preferred @@ -680,6 +773,53 @@ extension String { instance.write(to: &self) } + /// Creates a string representing the given value. + /// + /// Use this initializer to convert an instance of any type to its preferred + /// representation as a `String` instance. The initializer creates the + /// string representation of `instance` in one of the following ways, + /// depending on its protocol conformance: + /// + /// - If `instance` conforms to the `TextOutputStreamable` protocol, the + /// result is obtained by calling `instance.write(to: s)` on an empty + /// string `s`. + /// - If `instance` conforms to the `CustomStringConvertible` protocol, the + /// result is `instance.description`. + /// - If `instance` conforms to the `CustomDebugStringConvertible` protocol, + /// the result is `instance.debugDescription`. + /// - An unspecified result is supplied automatically by the Swift standard + /// library. + /// + /// For example, this custom `Point` struct uses the default representation + /// supplied by the standard library. + /// + /// struct Point { + /// let x: Int, y: Int + /// } + /// + /// let p = Point(x: 21, y: 30) + /// print(String(describing: p)) + /// // Prints "Point(x: 21, y: 30)" + /// + /// After adding `CustomStringConvertible` conformance by implementing the + /// `description` property, `Point` provides its own custom representation. + /// + /// extension Point: CustomStringConvertible { + /// var description: String { + /// return "(\(x), \(y))" + /// } + /// } + /// + /// print(String(describing: p)) + /// // Prints "(21, 30)" + @inlinable + @_alwaysEmitIntoClient + public init(describing instance: borrowing Subject) + where Subject: CustomStringConvertible & TextOutputStreamable & ~Copyable + { + self = instance.description + } + /// Creates a string representing the given value. /// /// Use this initializer to convert an instance of any type to its preferred diff --git a/stdlib/public/core/OutputStream.swift b/stdlib/public/core/OutputStream.swift index 37ee91263b2fa..3ddcbf9d728b9 100644 --- a/stdlib/public/core/OutputStream.swift +++ b/stdlib/public/core/OutputStream.swift @@ -99,7 +99,7 @@ extension TextOutputStream { /// To add `TextOutputStreamable` conformance to a custom type, implement the /// required `write(to:)` method. Call the given output stream's `write(_:)` /// method in your implementation. -public protocol TextOutputStreamable { +public protocol TextOutputStreamable: ~Copyable { /// Writes a textual representation of this instance into the given output /// stream. func write(to target: inout Target) @@ -147,7 +147,7 @@ public protocol TextOutputStreamable { /// /// print(p) /// // Prints "(21, 30)" -public protocol CustomStringConvertible { +public protocol CustomStringConvertible: ~Copyable { /// A textual representation of this instance. /// /// Calling this property directly is discouraged. Instead, convert an @@ -182,7 +182,7 @@ public protocol CustomStringConvertible { /// The description property of a conforming type must be a value-preserving /// representation of the original value. As such, it should be possible to /// re-create an instance from its string representation. -public protocol LosslessStringConvertible: CustomStringConvertible { +public protocol LosslessStringConvertible: CustomStringConvertible, ~Copyable { /// Instantiates an instance of the conforming type from a string /// representation. init?(_ description: String) @@ -239,7 +239,7 @@ public protocol LosslessStringConvertible: CustomStringConvertible { /// /// print(String(reflecting: p)) /// // Prints "(21, 30)" -public protocol CustomDebugStringConvertible { +public protocol CustomDebugStringConvertible: ~Copyable { /// A textual representation of this instance, suitable for debugging. /// /// Calling this property directly is discouraged. Instead, convert an diff --git a/stdlib/public/core/StringInterpolation.swift b/stdlib/public/core/StringInterpolation.swift index 62a2d492aa1bf..5c9132239af99 100644 --- a/stdlib/public/core/StringInterpolation.swift +++ b/stdlib/public/core/StringInterpolation.swift @@ -105,8 +105,9 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable /// print(message) /// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars." @inlinable - public mutating func appendInterpolation(_ value: T) - where T: TextOutputStreamable, T: CustomStringConvertible + @_preInverseGenerics + public mutating func appendInterpolation(_ value: borrowing T) + where T: TextOutputStreamable, T: CustomStringConvertible, T: ~Copyable { value.write(to: &self) } @@ -127,8 +128,9 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable /// print(message) /// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars." @inlinable - public mutating func appendInterpolation(_ value: T) - where T: TextOutputStreamable + @_preInverseGenerics + public mutating func appendInterpolation(_ value: borrowing T) + where T: TextOutputStreamable & ~Copyable { value.write(to: &self) } @@ -151,8 +153,9 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable /// print(message) /// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars." @inlinable - public mutating func appendInterpolation(_ value: T) - where T: CustomStringConvertible + @_preInverseGenerics + public mutating func appendInterpolation(_ value: borrowing T) + where T: CustomStringConvertible, T: ~Copyable { value.description.write(to: &self) } @@ -175,7 +178,7 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable /// print(message) /// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars." @inlinable - public mutating func appendInterpolation(_ value: T) { + public mutating func appendInterpolation(_ value: borrowing T) { #if !$Embedded _print_unlocked(value, &self) #else @@ -217,13 +220,15 @@ extension DefaultStringInterpolation { /// - value: The value to include in a string interpolation, if non-`nil`. /// - default: The string to include if `value` is `nil`. @_alwaysEmitIntoClient + @_preInverseGenerics public mutating func appendInterpolation( - _ value: T?, + _ value: borrowing T?, default: @autoclosure () -> some StringProtocol - ) where T: TextOutputStreamable, T: CustomStringConvertible { - if let value { + ) where T: TextOutputStreamable, T: CustomStringConvertible, T: ~Copyable { + switch value { + case let value?: self.appendInterpolation(value) - } else { + case nil: self.appendInterpolation(`default`()) } } @@ -247,13 +252,15 @@ extension DefaultStringInterpolation { /// - value: The value to include in a string interpolation, if non-`nil`. /// - default: The string to include if `value` is `nil`. @_alwaysEmitIntoClient + @_preInverseGenerics public mutating func appendInterpolation( - _ value: T?, + _ value: borrowing T?, default: @autoclosure () -> some StringProtocol - ) where T: TextOutputStreamable { - if let value { + ) where T: TextOutputStreamable & ~Copyable { + switch value { + case let value?: self.appendInterpolation(value) - } else { + case nil: self.appendInterpolation(`default`()) } } @@ -277,13 +284,15 @@ extension DefaultStringInterpolation { /// - value: The value to include in a string interpolation, if non-`nil`. /// - default: The string to include if `value` is `nil`. @_alwaysEmitIntoClient + @_preInverseGenerics public mutating func appendInterpolation( - _ value: T?, + _ value: borrowing T?, default: @autoclosure () -> some StringProtocol - ) where T: CustomStringConvertible { - if let value { + ) where T: CustomStringConvertible, T: ~Copyable { + switch value { + case let value?: self.appendInterpolation(value) - } else { + case nil: self.appendInterpolation(`default`()) } } @@ -311,9 +320,10 @@ extension DefaultStringInterpolation { _ value: T?, default: @autoclosure () -> some StringProtocol ) { - if let value { + switch value { + case let value?: self.appendInterpolation(value) - } else { + case nil: self.appendInterpolation(`default`()) } } diff --git a/test/api-digester/Outputs/stability-stdlib-source-base.swift.expected b/test/api-digester/Outputs/stability-stdlib-source-base.swift.expected index 6a7345ca8847a..4ba061781b4f1 100644 --- a/test/api-digester/Outputs/stability-stdlib-source-base.swift.expected +++ b/test/api-digester/Outputs/stability-stdlib-source-base.swift.expected @@ -53,6 +53,7 @@ Constructor ContiguousArray.init(unsafeUninitializedCapacity:initializingWith:) Constructor ContiguousArray.init(unsafeUninitializedCapacity:initializingWith:) has parameter 1 type change from (inout Swift.UnsafeMutableBufferPointer, inout Swift.Int) throws -> Swift.Void to (inout Swift.UnsafeMutableBufferPointer, inout Swift.Int) throws(E) -> Swift.Void Constructor ContiguousArray.init(unsafeUninitializedCapacity:initializingWith:) is now without rethrows +Protocol LosslessStringConvertible has generic signature change from to Protocol SIMDScalar has generic signature change from to Protocol AdditiveArithmetic has added inherited protocol Copyable @@ -72,7 +73,6 @@ Protocol CodingKey has added inherited protocol Escapable Protocol Collection has added inherited protocol Copyable Protocol Collection has added inherited protocol Escapable Protocol Comparable has added inherited protocol Escapable -Protocol CustomDebugStringConvertible has added inherited protocol Copyable Protocol CustomDebugStringConvertible has added inherited protocol Escapable Protocol CustomLeafReflectable has added inherited protocol Copyable Protocol CustomLeafReflectable has added inherited protocol Escapable @@ -80,7 +80,6 @@ Protocol CustomPlaygroundDisplayConvertible has added inherited protocol Copyabl Protocol CustomPlaygroundDisplayConvertible has added inherited protocol Escapable Protocol CustomReflectable has added inherited protocol Copyable Protocol CustomReflectable has added inherited protocol Escapable -Protocol CustomStringConvertible has added inherited protocol Copyable Protocol CustomStringConvertible has added inherited protocol Escapable Protocol Decodable has added inherited protocol Copyable Protocol Decodable has added inherited protocol Escapable @@ -129,7 +128,6 @@ Protocol LazyCollectionProtocol has added inherited protocol Copyable Protocol LazyCollectionProtocol has added inherited protocol Escapable Protocol LazySequenceProtocol has added inherited protocol Copyable Protocol LazySequenceProtocol has added inherited protocol Escapable -Protocol LosslessStringConvertible has added inherited protocol Copyable Protocol LosslessStringConvertible has added inherited protocol Escapable Protocol MirrorPath has added inherited protocol Copyable Protocol MirrorPath has added inherited protocol Escapable @@ -176,7 +174,6 @@ Protocol StringProtocol has added inherited protocol Copyable Protocol StringProtocol has added inherited protocol Escapable Protocol TextOutputStream has added inherited protocol Copyable Protocol TextOutputStream has added inherited protocol Escapable -Protocol TextOutputStreamable has added inherited protocol Copyable Protocol TextOutputStreamable has added inherited protocol Escapable Protocol UnicodeCodec has added inherited protocol Copyable Protocol UnicodeCodec has added inherited protocol Escapable @@ -401,3 +398,12 @@ Func Comparable.>=(_:_:) has generic signature change from =(_:_:) has parameter 0 changing from Default to Shared Func Comparable.>=(_:_:) has parameter 1 changing from Default to Shared +// CustomStringConvertible etc: ~Copyable +Accessor CustomDebugStringConvertible.debugDescription.Get() has generic signature change from to +Accessor CustomStringConvertible.description.Get() has generic signature change from to +Constructor LosslessStringConvertible.init(_:) has generic signature change from to +Func DefaultStringInterpolation.appendInterpolation(_:) has generic signature change from to +Func DefaultStringInterpolation.appendInterpolation(_:) has generic signature change from to +Func DefaultStringInterpolation.appendInterpolation(_:) has generic signature change from to +Func DefaultStringInterpolation.appendInterpolation(_:) has parameter 0 changing from Default to Shared +Func TextOutputStreamable.write(to:) has generic signature change from to diff --git a/test/api-digester/stability-stdlib-abi-without-asserts.test b/test/api-digester/stability-stdlib-abi-without-asserts.test index 1ccb4291c8fb6..acd952f777215 100644 --- a/test/api-digester/stability-stdlib-abi-without-asserts.test +++ b/test/api-digester/stability-stdlib-abi-without-asserts.test @@ -172,6 +172,7 @@ Subscript MutableCollection.subscript(_:) has been removed Protocol SIMDScalar has added inherited protocol BitwiseCopyable Protocol SIMDScalar has generic signature change from to Protocol _Pointer has added inherited protocol BitwiseCopyable +Protocol LosslessStringConvertible has generic signature change from to Protocol AdditiveArithmetic has added inherited protocol Copyable Protocol AdditiveArithmetic has added inherited protocol Escapable @@ -190,7 +191,6 @@ Protocol CodingKey has added inherited protocol Escapable Protocol Collection has added inherited protocol Copyable Protocol Collection has added inherited protocol Escapable Protocol Comparable has added inherited protocol Escapable -Protocol CustomDebugStringConvertible has added inherited protocol Copyable Protocol CustomDebugStringConvertible has added inherited protocol Escapable Protocol CustomLeafReflectable has added inherited protocol Copyable Protocol CustomLeafReflectable has added inherited protocol Escapable @@ -198,7 +198,6 @@ Protocol CustomPlaygroundDisplayConvertible has added inherited protocol Copyabl Protocol CustomPlaygroundDisplayConvertible has added inherited protocol Escapable Protocol CustomReflectable has added inherited protocol Copyable Protocol CustomReflectable has added inherited protocol Escapable -Protocol CustomStringConvertible has added inherited protocol Copyable Protocol CustomStringConvertible has added inherited protocol Escapable Protocol Decodable has added inherited protocol Copyable Protocol Decodable has added inherited protocol Escapable @@ -247,7 +246,6 @@ Protocol LazyCollectionProtocol has added inherited protocol Copyable Protocol LazyCollectionProtocol has added inherited protocol Escapable Protocol LazySequenceProtocol has added inherited protocol Copyable Protocol LazySequenceProtocol has added inherited protocol Escapable -Protocol LosslessStringConvertible has added inherited protocol Copyable Protocol LosslessStringConvertible has added inherited protocol Escapable Protocol MirrorPath has added inherited protocol Copyable Protocol MirrorPath has added inherited protocol Escapable @@ -293,7 +291,6 @@ Protocol StringProtocol has added inherited protocol Copyable Protocol StringProtocol has added inherited protocol Escapable Protocol TextOutputStream has added inherited protocol Copyable Protocol TextOutputStream has added inherited protocol Escapable -Protocol TextOutputStreamable has added inherited protocol Copyable Protocol TextOutputStreamable has added inherited protocol Escapable Protocol UnicodeCodec has added inherited protocol Copyable Protocol UnicodeCodec has added inherited protocol Escapable @@ -912,5 +909,18 @@ Func Comparable.>=(_:_:) has parameter 0 changing from Default to Shared Func Comparable.>=(_:_:) has parameter 1 changing from Default to Shared Func Comparable.>=(_:_:) is now with @_preInverseGenerics +// CustomStringConvertible etc: ~Copyable +Accessor CustomDebugStringConvertible.debugDescription.Get() has generic signature change from to +Accessor CustomStringConvertible.description.Get() has generic signature change from to +Constructor LosslessStringConvertible.init(_:) has generic signature change from to +Func DefaultStringInterpolation.appendInterpolation(_:) has generic signature change from to +Func DefaultStringInterpolation.appendInterpolation(_:) has generic signature change from to +Func DefaultStringInterpolation.appendInterpolation(_:) has generic signature change from to +Func DefaultStringInterpolation.appendInterpolation(_:) has mangled name changing from 'Swift.DefaultStringInterpolation.appendInterpolation(A) -> ()' to 'Swift.DefaultStringInterpolation.appendInterpolation(A) -> ()' +Func DefaultStringInterpolation.appendInterpolation(_:) has mangled name changing from 'Swift.DefaultStringInterpolation.appendInterpolation(A) -> ()' to 'Swift.DefaultStringInterpolation.appendInterpolation(A) -> ()' +Func DefaultStringInterpolation.appendInterpolation(_:) has mangled name changing from 'Swift.DefaultStringInterpolation.appendInterpolation(A) -> ()' to 'Swift.DefaultStringInterpolation.appendInterpolation(A) -> ()' +Func DefaultStringInterpolation.appendInterpolation(_:) has parameter 0 changing from Default to Shared +Func DefaultStringInterpolation.appendInterpolation(_:) is now with @_preInverseGenerics +Func TextOutputStreamable.write(to:) has generic signature change from to // *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.) diff --git a/test/attr/attr_abi.swift b/test/attr/attr_abi.swift index cda09f72eb53d..d19bf4abe6fc3 100644 --- a/test/attr/attr_abi.swift +++ b/test/attr/attr_abi.swift @@ -1024,7 +1024,7 @@ func testNormalProtocols( ) {} @abi( - func testNormalProtocolsGeneric( // expected-error {{generic signature '' in '@abi' is not compatible with ''}} + func testNormalProtocolsGeneric( // expected-error {{generic signature '' in '@abi' is not compatible with ''}} _: A, _: B ) ) diff --git a/test/stdlib/NoncopyableCustomStringConvertible.swift b/test/stdlib/NoncopyableCustomStringConvertible.swift new file mode 100644 index 0000000000000..8f53fad4cee75 --- /dev/null +++ b/test/stdlib/NoncopyableCustomStringConvertible.swift @@ -0,0 +1,67 @@ +//===--- NoncopyableEquatable.swift - tests for Equatable: ~Copyable ---------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// RUN: %target-run-simple-swift +// REQUIRES: executable_test + +import StdlibUnittest + +let NoncopyableCustomStringConvertibleTests = TestSuite("NoncopyableCustomStringConvertible") + +struct Noncopyable: ~Copyable { + var wrapped: Wrapped +} + +// This approach is not necessarily what we would expect users to do, but for now we do +// not have a generalized solution for reflection-based stringifying of noncopyable types, +// so using this technique for testing purposes. +extension Noncopyable: CustomStringConvertible where Wrapped: CustomStringConvertible & ~Copyable { + var description: String { + "No copying the value \(wrapped)" + } + } + +extension Noncopyable: CustomDebugStringConvertible where Wrapped: CustomStringConvertible & ~Copyable { + var debugDescription: String { + "Noncopyable(wrapping: \(wrapped))" + } + } + +extension Noncopyable: LosslessStringConvertible { + init?(_ description: String) { + guard let i: Int = .init(description) else { return nil } + self.wrapped = i + } +} + +extension LosslessStringConvertible where Self: ~Copyable { + init(riskily with: String) { + self = .init(with)! + } +} + +func debug(value: borrowing some CustomDebugStringConvertible & ~Copyable) -> String { + value.debugDescription +} + +NoncopyableCustomStringConvertibleTests.test("noncopyable interpolation") { + let a: Noncopyable = .init(riskily: "99") + let b: Noncopyable> = Noncopyable(wrapped: Noncopyable(riskily: "42")) + + expectEqual("NC: \(a)", "NC: No copying the value 99") + expectEqual("Noncopyable(wrapping: 99)", debug(value: a)) + + expectEqual("NC: \(b)", "NC: No copying the value No copying the value 42") + expectEqual("Noncopyable(wrapping: No copying the value 42)", debug(value: b)) + +} + +runAllTests()