From 04a26d311987518f64f3e48b4f56966170caeb75 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 26 Sep 2025 23:48:42 -0400 Subject: [PATCH] Unconditionally conform `Attachment` to `CustomStringConvertible`. This PR adjusts `Attachment`'s conformance to `CustomStringConvertible` such that it conforms even when `AttachableValue` is not `Copyable`. We now check at runtime (by way of protocol shenanigans, of course) whether the attachable value is copyable and, if so, take a different code path than we take if it does not conform. --- Sources/Testing/Attachments/Attachment.swift | 31 +++++++++++++------- Tests/TestingTests/AttachmentTests.swift | 6 ++-- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Sources/Testing/Attachments/Attachment.swift b/Sources/Testing/Attachments/Attachment.swift index b665b99fe..932a1bcf7 100644 --- a/Sources/Testing/Attachments/Attachment.swift +++ b/Sources/Testing/Attachments/Attachment.swift @@ -97,6 +97,19 @@ public struct Attachment where AttachableValue: Attachable & ~C extension Attachment: Sendable where AttachableValue: Sendable {} extension Attachment.Storage: Sendable where AttachableValue: Sendable {} +#if !hasFeature(Embedded) +/// A protocol that describes an attachment with a copyable value. +/// +/// We can use this protocol to make runtime decisions about attachments based +/// on whether or not their attachable values are copyable. +private protocol _AttachmentWithCopyableValue { + associatedtype AttachableValue: Attachable & Copyable + var attachableValue: AttachableValue { get } +} + +extension Attachment: _AttachmentWithCopyableValue where AttachableValue: Copyable {} +#endif + // MARK: - Initializing an attachment extension Attachment where AttachableValue: ~Copyable { @@ -180,21 +193,19 @@ public struct AnyAttachable: AttachableWrapper, Sendable, Copyable { // MARK: - Describing an attachment -extension Attachment where AttachableValue: ~Copyable { - @_documentation(visibility: private) - public var description: String { - let typeInfo = TypeInfo(describing: AttachableValue.self) - return #""\#(preferredName)": instance of '\#(typeInfo.unqualifiedName)'"# - } -} - -extension Attachment: CustomStringConvertible { +extension Attachment: CustomStringConvertible where AttachableValue: ~Copyable { /// @Metadata { /// @Available(Swift, introduced: 6.2) /// @Available(Xcode, introduced: 26.0) /// } public var description: String { - #""\#(preferredName)": \#(String(describingForTest: attachableValue))"# +#if !hasFeature(Embedded) + if let selfCopy = self as? any _AttachmentWithCopyableValue { + return #""\#(preferredName)": \#(String(describingForTest: selfCopy.attachableValue))"# + } +#endif + let typeInfo = TypeInfo(describing: AttachableValue.self) + return #""\#(preferredName)": instance of '\#(typeInfo.unqualifiedName)'"# } } diff --git a/Tests/TestingTests/AttachmentTests.swift b/Tests/TestingTests/AttachmentTests.swift index 7695dd634..2b8d3e840 100644 --- a/Tests/TestingTests/AttachmentTests.swift +++ b/Tests/TestingTests/AttachmentTests.swift @@ -50,15 +50,15 @@ struct AttachmentTests { let attachableValue = MySendableAttachable(string: "") let attachment = Attachment(attachableValue, named: "AttachmentTests.saveValue.html") #expect(String(describing: attachment).contains(#""\#(attachment.preferredName)""#)) - #expect(attachment.description.contains("MySendableAttachable(")) + #expect(String(describing: attachment).contains("MySendableAttachable(")) } #if compiler(>=6.3) || !os(Windows) // WORKAROUND: swift-#84184 @Test func moveOnlyDescription() { let attachableValue = MyAttachable(string: "") let attachment = Attachment(attachableValue, named: "AttachmentTests.saveValue.html") - #expect(attachment.description.contains(#""\#(attachment.preferredName)""#)) - #expect(attachment.description.contains("'MyAttachable'")) + #expect(String(describing: attachment).contains(#""\#(attachment.preferredName)""#)) + #expect(String(describing: attachment).contains("'MyAttachable'")) } #endif