From d5c4d5ab01a4d96ebe359fc4edd0d3e45b6cc8fa Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 23 Sep 2025 12:45:57 -0400 Subject: [PATCH 1/4] Promote `SourceLocation._filePath` to proper, non-underscored SPI. This PR introduces an `@_spi var filePath` property on `SourceLocation` with the intent of replacing the existing underscored/unsupported `_filePath` property. Because Xcode 16 through 26 directly encodes and decodes instances of `SourceLocation`, we can't just stop encoding/decoding the `_filePath` key and need to keep it there for at least a Swift release or two, until Apple's fork of Swift Testing has been updated to include the new encoding key. At that point we can remove the old one. --- .../SourceAttribution/SourceLocation.swift | 61 ++++++++++++++++--- Tests/TestingTests/SourceLocationTests.swift | 10 +++ 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/Sources/Testing/SourceAttribution/SourceLocation.swift b/Sources/Testing/SourceAttribution/SourceLocation.swift index 3aca54d2f..918eaf9c8 100644 --- a/Sources/Testing/SourceAttribution/SourceLocation.swift +++ b/Sources/Testing/SourceAttribution/SourceLocation.swift @@ -71,11 +71,8 @@ public struct SourceLocation: Sendable { } /// The path to the source file. - /// - /// - Warning: This property is provided temporarily to aid in integrating the - /// testing library with existing tools such as Swift Package Manager. It - /// will be removed in a future release. - public var _filePath: String + @_spi(Experimental) + public var filePath: String /// The line in the source file. /// @@ -118,7 +115,7 @@ public struct SourceLocation: Sendable { precondition(column > 0, "SourceLocation.column must be greater than 0 (was \(column))") self.fileID = fileID - self._filePath = filePath + self.filePath = filePath self.line = line self.column = column } @@ -167,4 +164,54 @@ extension SourceLocation: CustomStringConvertible, CustomDebugStringConvertible // MARK: - Codable -extension SourceLocation: Codable {} +extension SourceLocation: Codable { + private enum CodingKeys: String, CodingKey { + case fileID + case filePath + case _filePath + case line + case column + } + + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fileID, forKey: .fileID) + try container.encode(line, forKey: .line) + try container.encode(column, forKey: .column) + + // For backwards-compatibility, we must always encode "_filePath". + try container.encode(filePath, forKey: ._filePath) + try container.encode(filePath, forKey: .filePath) + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + fileID = try container.decode(String.self, forKey: .fileID) + line = try container.decode(Int.self, forKey: .line) + column = try container.decode(Int.self, forKey: .column) + + // For simplicity's sake, we won't be picky about which key contains the + // file path. + filePath = try container.decodeIfPresent(String.self, forKey: ._filePath) + ?? try container.decode(String.self, forKey: .filePath) + } +} + +// MARK: - Deprecated + +extension SourceLocation { + /// The path to the source file. + /// + /// - Warning: This property is provided temporarily to aid in integrating the + /// testing library with existing tools such as Swift Package Manager. It + /// will be removed in a future release. + @available(swift, deprecated: 100000.0, renamed: "filePath") + public var _filePath: String { + get { + filePath + } + set { + filePath = newValue + } + } +} diff --git a/Tests/TestingTests/SourceLocationTests.swift b/Tests/TestingTests/SourceLocationTests.swift index 4145687b8..d504ca02c 100644 --- a/Tests/TestingTests/SourceLocationTests.swift +++ b/Tests/TestingTests/SourceLocationTests.swift @@ -121,6 +121,16 @@ struct SourceLocationTests { } #endif + @Test("SourceLocation.filePath property") + func sourceLocationFilePath() { + var sourceLocation = #_sourceLocation + #expect(sourceLocation.filePath == #filePath) + + sourceLocation.filePath = "A" + #expect(sourceLocation.filePath == "A") + } + + @available(swift, deprecated: 100000.0) @Test("SourceLocation._filePath property") func sourceLocationFilePath() { var sourceLocation = #_sourceLocation From a331c6a0f97f036e6635259ff384e7113c0332aa Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 23 Sep 2025 13:16:59 -0400 Subject: [PATCH 2/4] Fix typo --- Sources/Testing/SourceAttribution/SourceLocation.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Testing/SourceAttribution/SourceLocation.swift b/Sources/Testing/SourceAttribution/SourceLocation.swift index 918eaf9c8..ff22a495b 100644 --- a/Sources/Testing/SourceAttribution/SourceLocation.swift +++ b/Sources/Testing/SourceAttribution/SourceLocation.swift @@ -165,7 +165,7 @@ extension SourceLocation: CustomStringConvertible, CustomDebugStringConvertible // MARK: - Codable extension SourceLocation: Codable { - private enum CodingKeys: String, CodingKey { + private enum _CodingKeys: String, CodingKey { case fileID case filePath case _filePath @@ -173,8 +173,8 @@ extension SourceLocation: Codable { case column } - func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: _CodingKeys.self) try container.encode(fileID, forKey: .fileID) try container.encode(line, forKey: .line) try container.encode(column, forKey: .column) @@ -184,8 +184,8 @@ extension SourceLocation: Codable { try container.encode(filePath, forKey: .filePath) } - init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: _CodingKeys.self) fileID = try container.decode(String.self, forKey: .fileID) line = try container.decode(Int.self, forKey: .line) column = try container.decode(Int.self, forKey: .column) From 4176840aa3b78c8620de6be92d44928be8c3f38f Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 23 Sep 2025 15:38:48 -0400 Subject: [PATCH 3/4] Incorporate feedback --- Sources/Testing/SourceAttribution/SourceLocation.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/Testing/SourceAttribution/SourceLocation.swift b/Sources/Testing/SourceAttribution/SourceLocation.swift index ff22a495b..f5af81543 100644 --- a/Sources/Testing/SourceAttribution/SourceLocation.swift +++ b/Sources/Testing/SourceAttribution/SourceLocation.swift @@ -168,9 +168,11 @@ extension SourceLocation: Codable { private enum _CodingKeys: String, CodingKey { case fileID case filePath - case _filePath case line case column + + /// A backwards-compatible synonym of ``filePath``. + case _filePath } public func encode(to encoder: any Encoder) throws { @@ -192,8 +194,8 @@ extension SourceLocation: Codable { // For simplicity's sake, we won't be picky about which key contains the // file path. - filePath = try container.decodeIfPresent(String.self, forKey: ._filePath) - ?? try container.decode(String.self, forKey: .filePath) + filePath = try container.decodeIfPresent(String.self, forKey: .filePath) + ?? container.decode(String.self, forKey: ._filePath) } } From d53c49ac5b91dd4a7d4c9d919ceafcc93c4fa2ae Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 23 Sep 2025 15:55:26 -0400 Subject: [PATCH 4/4] Fix typo --- Tests/TestingTests/SourceLocationTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/TestingTests/SourceLocationTests.swift b/Tests/TestingTests/SourceLocationTests.swift index d504ca02c..2f9dc9b7e 100644 --- a/Tests/TestingTests/SourceLocationTests.swift +++ b/Tests/TestingTests/SourceLocationTests.swift @@ -132,7 +132,7 @@ struct SourceLocationTests { @available(swift, deprecated: 100000.0) @Test("SourceLocation._filePath property") - func sourceLocationFilePath() { + func sourceLocation_FilePath() { var sourceLocation = #_sourceLocation #expect(sourceLocation._filePath == #filePath)