Skip to content

Commit b3c34c4

Browse files
committed
Replace #fileID, #filePath, #line, and #column with a single macro.
We have a bunch of functions that take separate, defaulted arguments for file ID, column, etc. We'd like to normalize these functions to always take a single `SourceLocation` instance, but until now there hasn't been a way to express such a thing as a default argument without capturing the wrong source location. This PR takes advantage of [SE-0422](https://github.com/apple/swift-evolution/blob/main/proposals/0422-caller-side-default-argument-macro-expression.md) to create a new `#here` macro that does what we want here. (We can't use `#sourceLocation` because the language already reserves that macro name for an unrelated use.) Because this is a new Swift 6 feature, we cannot rely on it until we drop our remaining Swift 5.10 support. Adding `#if compiler(>=5.11)` would require extensive changes and code duplication.
1 parent 33ffe33 commit b3c34c4

23 files changed

+298
-249
lines changed

Sources/Testing/Expectations/Expectation+Macro.swift

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
@freestanding(expression) public macro expect(
2424
_ condition: Bool,
2525
_ comment: @autoclosure () -> Comment? = nil,
26-
sourceLocation: SourceLocation = SourceLocation()
26+
sourceLocation: SourceLocation = #here
2727
) = #externalMacro(module: "TestingMacros", type: "ExpectMacro")
2828

2929
/// Check that an expectation has passed after a condition has been evaluated
@@ -44,7 +44,7 @@
4444
@freestanding(expression) public macro require(
4545
_ condition: Bool,
4646
_ comment: @autoclosure () -> Comment? = nil,
47-
sourceLocation: SourceLocation = SourceLocation()
47+
sourceLocation: SourceLocation = #here
4848
) = #externalMacro(module: "TestingMacros", type: "RequireMacro")
4949

5050
// MARK: - Optional checking
@@ -68,7 +68,7 @@
6868
@freestanding(expression) public macro require<T>(
6969
_ optionalValue: T?,
7070
_ comment: @autoclosure () -> Comment? = nil,
71-
sourceLocation: SourceLocation = SourceLocation()
71+
sourceLocation: SourceLocation = #here
7272
) -> T = #externalMacro(module: "TestingMacros", type: "RequireMacro")
7373

7474
/// Unwrap an optional boolean value or, if it is `nil`, fail and throw an
@@ -98,7 +98,7 @@
9898
public macro require(
9999
_ optionalValue: Bool?,
100100
_ comment: @autoclosure () -> Comment? = nil,
101-
sourceLocation: SourceLocation = SourceLocation()
101+
sourceLocation: SourceLocation = #here
102102
) -> Bool = #externalMacro(module: "TestingMacros", type: "AmbiguousRequireMacro")
103103

104104
// MARK: - Matching errors by type
@@ -136,7 +136,7 @@ public macro require(
136136
@freestanding(expression) public macro expect<E, R>(
137137
throws errorType: E.Type,
138138
_ comment: @autoclosure () -> Comment? = nil,
139-
sourceLocation: SourceLocation = SourceLocation(),
139+
sourceLocation: SourceLocation = #here,
140140
performing expression: () async throws -> R
141141
) = #externalMacro(module: "TestingMacros", type: "ExpectMacro") where E: Error
142142

@@ -175,7 +175,7 @@ public macro require(
175175
@freestanding(expression) public macro expect<R>(
176176
throws _: Never.Type,
177177
_ comment: @autoclosure () -> Comment? = nil,
178-
sourceLocation: SourceLocation = SourceLocation(),
178+
sourceLocation: SourceLocation = #here,
179179
performing expression: () async throws -> R
180180
) = #externalMacro(module: "TestingMacros", type: "ExpectMacro")
181181

@@ -217,7 +217,7 @@ public macro require(
217217
@freestanding(expression) public macro require<E, R>(
218218
throws errorType: E.Type,
219219
_ comment: @autoclosure () -> Comment? = nil,
220-
sourceLocation: SourceLocation = SourceLocation(),
220+
sourceLocation: SourceLocation = #here,
221221
performing expression: () async throws -> R
222222
) = #externalMacro(module: "TestingMacros", type: "RequireMacro") where E: Error
223223

@@ -236,7 +236,7 @@ public macro require(
236236
@freestanding(expression) public macro require<R>(
237237
throws _: Never.Type,
238238
_ comment: @autoclosure () -> Comment? = nil,
239-
sourceLocation: SourceLocation = SourceLocation(),
239+
sourceLocation: SourceLocation = #here,
240240
performing expression: () async throws -> R
241241
) = #externalMacro(module: "TestingMacros", type: "RequireMacro")
242242

@@ -272,7 +272,7 @@ public macro require(
272272
@freestanding(expression) public macro expect<E, R>(
273273
throws error: E,
274274
_ comment: @autoclosure () -> Comment? = nil,
275-
sourceLocation: SourceLocation = SourceLocation(),
275+
sourceLocation: SourceLocation = #here,
276276
performing expression: () async throws -> R
277277
) = #externalMacro(module: "TestingMacros", type: "ExpectMacro") where E: Error & Equatable
278278

@@ -309,7 +309,7 @@ public macro require(
309309
@freestanding(expression) public macro require<E, R>(
310310
throws error: E,
311311
_ comment: @autoclosure () -> Comment? = nil,
312-
sourceLocation: SourceLocation = SourceLocation(),
312+
sourceLocation: SourceLocation = #here,
313313
performing expression: () async throws -> R
314314
) = #externalMacro(module: "TestingMacros", type: "RequireMacro") where E: Error & Equatable
315315

@@ -352,7 +352,7 @@ public macro require(
352352
/// ``expect(throws:_:sourceLocation:performing:)-5lzjz`` instead.
353353
@freestanding(expression) public macro expect<R>(
354354
_ comment: @autoclosure () -> Comment? = nil,
355-
sourceLocation: SourceLocation = SourceLocation(),
355+
sourceLocation: SourceLocation = #here,
356356
performing expression: () async throws -> R,
357357
throws errorMatcher: (any Error) async throws -> Bool
358358
) = #externalMacro(module: "TestingMacros", type: "ExpectMacro")
@@ -400,7 +400,7 @@ public macro require(
400400
/// this macro. The test will then fail if an error is thrown.
401401
@freestanding(expression) public macro require<R>(
402402
_ comment: @autoclosure () -> Comment? = nil,
403-
sourceLocation: SourceLocation = SourceLocation(),
403+
sourceLocation: SourceLocation = #here,
404404
performing expression: () async throws -> R,
405405
throws errorMatcher: (any Error) async throws -> Bool
406406
) = #externalMacro(module: "TestingMacros", type: "RequireMacro")
@@ -474,7 +474,7 @@ public macro require(
474474
@freestanding(expression) public macro expect(
475475
exitsWith expectedExitCondition: ExitCondition,
476476
_ comment: @autoclosure () -> Comment? = nil,
477-
sourceLocation: SourceLocation = SourceLocation(),
477+
sourceLocation: SourceLocation = #here,
478478
performing expression: @convention(thin) () async throws -> Void
479479
) = #externalMacro(module: "TestingMacros", type: "ExitTestExpectMacro")
480480

@@ -549,6 +549,6 @@ public macro require(
549549
@freestanding(expression) public macro require(
550550
exitsWith expectedExitCondition: ExitCondition,
551551
_ comment: @autoclosure () -> Comment? = nil,
552-
sourceLocation: SourceLocation = SourceLocation(),
552+
sourceLocation: SourceLocation = #here,
553553
performing expression: @convention(thin) () async -> Void
554554
) = #externalMacro(module: "TestingMacros", type: "ExitTestRequireMacro")

Sources/Testing/Issues/Confirmation.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,7 @@ extension Confirmation {
9292
public func confirmation<R>(
9393
_ comment: Comment? = nil,
9494
expectedCount: Int = 1,
95-
fileID: String = #fileID,
96-
filePath: String = #filePath,
97-
line: Int = #line,
98-
column: Int = #column,
95+
sourceLocation: SourceLocation = #here,
9996
_ body: (Confirmation) async throws -> R
10097
) async rethrows -> R {
10198
let confirmation = Confirmation()
@@ -106,7 +103,7 @@ public func confirmation<R>(
106103
.confirmationMiscounted(actual: actualCount, expected: expectedCount),
107104
comments: Array(comment),
108105
backtrace: .current(),
109-
sourceLocation: SourceLocation(fileID: fileID, filePath: filePath, line: line, column: column)
106+
sourceLocation: sourceLocation
110107
)
111108
}
112109
}

Sources/Testing/Issues/Issue+Recording.swift

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,8 @@ extension Issue {
107107
/// or ``require(_:_:sourceLocation:)-5l63q`` macros.)
108108
@discardableResult public static func record(
109109
_ comment: Comment? = nil,
110-
fileID: String = #fileID,
111-
filePath: String = #filePath,
112-
line: Int = #line,
113-
column: Int = #column
110+
sourceLocation: SourceLocation = #here
114111
) -> Self {
115-
let sourceLocation = SourceLocation(fileID: fileID, filePath: filePath, line: line, column: column)
116112
let sourceContext = SourceContext(backtrace: .current(), sourceLocation: sourceLocation)
117113
let issue = Issue(kind: .unconditional, comments: Array(comment), sourceContext: sourceContext)
118114
return issue.record()
@@ -137,12 +133,8 @@ extension Issue {
137133
@discardableResult public static func record(
138134
_ error: any Error,
139135
_ comment: Comment? = nil,
140-
fileID: String = #fileID,
141-
filePath: String = #filePath,
142-
line: Int = #line,
143-
column: Int = #column
136+
sourceLocation: SourceLocation = #here
144137
) -> Self {
145-
let sourceLocation = SourceLocation(fileID: fileID, filePath: filePath, line: line, column: column)
146138
let backtrace = Backtrace(forFirstThrowOf: error) ?? Backtrace.current()
147139
let sourceContext = SourceContext(backtrace: backtrace, sourceLocation: sourceLocation)
148140
let issue = Issue(kind: .errorCaught(error), comments: Array(comment), sourceContext: sourceContext)

Sources/Testing/Issues/Issue.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public struct Issue: Sendable {
1313
/// Kinds of issues which may be recorded.
1414
public enum Kind: Sendable {
1515
/// An issue which occurred unconditionally, for example by using
16-
/// ``Issue/record(_:fileID:filePath:line:column:)``.
16+
/// ``Issue/record(_:sourceLocation:)``.
1717
case unconditional
1818

1919
/// An issue due to a failed expectation, such as those produced by
@@ -33,9 +33,9 @@ public struct Issue: Sendable {
3333
/// ``Confirmation/confirm(count:)`` should have been called.
3434
///
3535
/// This issue can occur when calling
36-
/// ``confirmation(_:expectedCount:fileID:filePath:line:column:_:)`` when
37-
/// the confirmation passed to these functions' `body` closures is confirmed
38-
/// too few or too many times.
36+
/// ``confirmation(_:expectedCount:sourceLocation:_:)`` when the
37+
/// confirmation passed to these functions' `body` closures is confirmed too
38+
/// few or too many times.
3939
indirect case confirmationMiscounted(actual: Int, expected: Int)
4040

4141
/// An issue due to an `Error` being thrown by a test function and caught by
@@ -235,7 +235,7 @@ extension Issue.Kind {
235235
@_spi(ForToolsIntegrationOnly)
236236
public enum Snapshot: Sendable, Codable {
237237
/// An issue which occurred unconditionally, for example by using
238-
/// ``Issue/record(_:fileID:filePath:line:column:)``.
238+
/// ``Issue/record(_:sourceLocation:)``.
239239
case unconditional
240240

241241
/// An issue due to a failed expectation, such as those produced by
@@ -255,9 +255,9 @@ extension Issue.Kind {
255255
/// ``Confirmation/confirm(count:)`` should have been called.
256256
///
257257
/// This issue can occur when calling
258-
/// ``confirmation(_:expectedCount:fileID:filePath:line:column:_:)`` when
259-
/// the confirmation passed to these functions' `body` closures is confirmed
260-
/// too few or too many times.
258+
/// ``confirmation(_:expectedCount:sourceLocation:_:)`` when the
259+
/// confirmation passed to these functions' `body` closures is confirmed too
260+
/// few or too many times.
261261
indirect case confirmationMiscounted(actual: Int, expected: Int)
262262

263263
/// An issue due to an `Error` being thrown by a test function and caught by

Sources/Testing/Issues/KnownIssue.swift

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -110,18 +110,15 @@ public typealias KnownIssueMatcher = @Sendable (_ issue: Issue) -> Bool
110110
/// Because all errors thrown by `body` are caught as known issues, this
111111
/// function is not throwing. If only some errors or issues are known to occur
112112
/// while others should continue to cause test failures, use
113-
/// ``withKnownIssue(_:isIntermittent:fileID:filePath:line:column:_:when:matching:)-68e5g``
113+
/// ``withKnownIssue(_:isIntermittent:sourceLocation:_:when:matching:)-5vi5n``
114114
/// instead.
115115
public func withKnownIssue(
116116
_ comment: Comment? = nil,
117117
isIntermittent: Bool = false,
118-
fileID: String = #fileID,
119-
filePath: String = #filePath,
120-
line: Int = #line,
121-
column: Int = #column,
118+
sourceLocation: SourceLocation = #here,
122119
_ body: () throws -> Void
123120
) {
124-
try? withKnownIssue(comment, isIntermittent: isIntermittent, fileID: fileID, filePath: filePath, line: line, column: column, body, matching: { _ in true })
121+
try? withKnownIssue(comment, isIntermittent: isIntermittent, sourceLocation: sourceLocation, body, matching: { _ in true })
125122
}
126123

127124
/// Invoke a function that has a known issue that is expected to occur during
@@ -164,17 +161,14 @@ public func withKnownIssue(
164161
///
165162
/// It is not necessary to specify both `precondition` and `issueMatcher` if
166163
/// only one is relevant. If all errors and issues should be considered known
167-
/// issues, use ``withKnownIssue(_:isIntermittent:fileID:filePath:line:column:_:)-5pxnd``
164+
/// issues, use ``withKnownIssue(_:isIntermittent:sourceLocation:_:)-95r6o``
168165
/// instead.
169166
///
170167
/// - Note: `issueMatcher` may be invoked more than once for the same issue.
171168
public func withKnownIssue(
172169
_ comment: Comment? = nil,
173170
isIntermittent: Bool = false,
174-
fileID: String = #fileID,
175-
filePath: String = #filePath,
176-
line: Int = #line,
177-
column: Int = #column,
171+
sourceLocation: SourceLocation = #here,
178172
_ body: () throws -> Void,
179173
when precondition: () -> Bool = { true },
180174
matching issueMatcher: @escaping KnownIssueMatcher = { _ in true }
@@ -183,7 +177,6 @@ public func withKnownIssue(
183177
return try body()
184178
}
185179
let matchCounter = Locked(rawValue: 0)
186-
let sourceLocation = SourceLocation(fileID: fileID, filePath: filePath, line: line, column: column)
187180
let issueMatcher = _combineIssueMatcher(issueMatcher, matchesCountedBy: matchCounter)
188181
defer {
189182
if !isIntermittent {
@@ -225,18 +218,15 @@ public func withKnownIssue(
225218
/// Because all errors thrown by `body` are caught as known issues, this
226219
/// function is not throwing. If only some errors or issues are known to occur
227220
/// while others should continue to cause test failures, use
228-
/// ``withKnownIssue(_:isIntermittent:fileID:filePath:line:column:_:when:matching:)-7azqg``
221+
/// ``withKnownIssue(_:isIntermittent:sourceLocation:_:when:matching:)-47y3z``
229222
/// instead.
230223
public func withKnownIssue(
231224
_ comment: Comment? = nil,
232225
isIntermittent: Bool = false,
233-
fileID: String = #fileID,
234-
filePath: String = #filePath,
235-
line: Int = #line,
236-
column: Int = #column,
226+
sourceLocation: SourceLocation = #here,
237227
_ body: () async throws -> Void
238228
) async {
239-
try? await withKnownIssue(comment, isIntermittent: isIntermittent, fileID: fileID, filePath: filePath, line: line, column: column, body, matching: { _ in true })
229+
try? await withKnownIssue(comment, isIntermittent: isIntermittent, sourceLocation: sourceLocation, body, matching: { _ in true })
240230
}
241231

242232
/// Invoke a function that has a known issue that is expected to occur during
@@ -279,17 +269,14 @@ public func withKnownIssue(
279269
///
280270
/// It is not necessary to specify both `precondition` and `issueMatcher` if
281271
/// only one is relevant. If all errors and issues should be considered known
282-
/// issues, use ``withKnownIssue(_:isIntermittent:fileID:filePath:line:column:_:)-30kgk``
272+
/// issues, use ``withKnownIssue(_:isIntermittent:sourceLocation:_:)-3g6b7``
283273
/// instead.
284274
///
285275
/// - Note: `issueMatcher` may be invoked more than once for the same issue.
286276
public func withKnownIssue(
287277
_ comment: Comment? = nil,
288278
isIntermittent: Bool = false,
289-
fileID: String = #fileID,
290-
filePath: String = #filePath,
291-
line: Int = #line,
292-
column: Int = #column,
279+
sourceLocation: SourceLocation = #here,
293280
_ body: () async throws -> Void,
294281
when precondition: () async -> Bool = { true },
295282
matching issueMatcher: @escaping KnownIssueMatcher = { _ in true }
@@ -298,7 +285,6 @@ public func withKnownIssue(
298285
return try await body()
299286
}
300287
let matchCounter = Locked(rawValue: 0)
301-
let sourceLocation = SourceLocation(fileID: fileID, filePath: filePath, line: line, column: column)
302288
let issueMatcher = _combineIssueMatcher(issueMatcher, matchesCountedBy: matchCounter)
303289
defer {
304290
if !isIntermittent {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
//
10+
11+
#if compiler(>=5.11)
12+
/// Get the current source location as a compile-time constant.
13+
///
14+
/// - Returns: The source location at which this macro is applied.
15+
///
16+
/// This macro can be used in place of `#fileID`, `#line`, and `#column` as a
17+
/// default argument to a function. It expands to an instance of
18+
/// ``SourceLocation`` referring to the location of the macro invocation itself
19+
/// (similar to how `#fileID` expands to the ID of the file containing the
20+
/// `#fileID` invocation.)
21+
@freestanding(expression) public macro here() -> SourceLocation = #externalMacro(module: "TestingMacros", type: "SourceLocationMacro")
22+
23+
extension SourceLocation {
24+
/// Get the current source location as an instance of ``SourceLocation``.
25+
///
26+
/// - Warning: This function is used to implement the `#here` macro. Do not
27+
/// call it directly.
28+
public static func __here(
29+
fileID: String = #fileID,
30+
filePath: String = #filePath,
31+
line: Int = #line,
32+
column: Int = #column
33+
) -> Self {
34+
Self(fileID: fileID, filePath: filePath, line: line, column: column)
35+
}
36+
}
37+
#endif

Sources/Testing/SourceAttribution/SourceLocation.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,7 @@ public struct SourceLocation: Sendable {
8787
}
8888
}
8989

90-
public init(
91-
fileID: String = #fileID,
92-
filePath: String = #filePath,
93-
line: Int = #line,
94-
column: Int = #column
95-
) {
90+
public init(fileID: String, filePath: String, line: Int, column: Int) {
9691
self.fileID = fileID
9792
self._filePath = filePath
9893
self.line = line

Sources/Testing/Testing.docc/EnablingAndDisabling.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ automatically skip them if conditions like these are not met.
2525
### Disable a test
2626

2727
If you need to disable a test unconditionally, use the
28-
``Trait/disabled(_:fileID:filePath:line:column:)`` function. Given the following
29-
test function:
28+
``Trait/disabled(_:sourceLocation:)`` function. Given the following test
29+
function:
3030

3131
```swift
3232
@Test("Food truck sells burritos")
@@ -62,7 +62,7 @@ func isCold() async throws { ... }
6262

6363
If it's currently winter, then presumably ice cream won't be available for
6464
sale and this test will fail. It therefore makes sense to only enable it if it's currently summer. You can conditionally enable a test with
65-
``Trait/enabled(if:_:fileID:filePath:line:column:)``:
65+
``Trait/enabled(if:_:sourceLocation:)``:
6666

6767
```swift
6868
@Test("Ice cream is cold", .enabled(if: Season.current == .summer))

0 commit comments

Comments
 (0)