0027-SOURCELOCATION-MACRO: Macro for getting the current source location
- Proposal: ST-0027
- Authors: Jonathan Grynspan
- Review Manager: Stuart Montgomery
- Status: Active Review (August 3...August 13, 2026)
- Bug: rdar://178259171
- Implementation: swiftlang/swift-testing#1733
- Review: (pitch) (review)
Introduction
Swift Testing includes a type, SourceLocation, that represents the precise location of something in a file (typically a .swift file). Various Swift Testing API takes an instance of this type in order to correctly attribute diagnostics and test issues that occur at test time. This proposal covers introducing a macro that can be used as a default argument to such functions.
Motivation
The Swift standard library includes macros to get the current file ID, file path, line, and column at compile time. These macros can then be used as default function arguments to allow automagical capture of the caller's location in source. For example, fatalError()) takes the file and line number and prints them to stderr when called.
Swift Testing needs to capture all four of these values, which is quite verbose and somewhat tedious to work with, so various Swift Testing APIs encapsulate all of them in a single argument of type SourceLocation. Swift Testing provides a #_sourceLocation macro that expands, at compile time, to an appropriate expression:
public func withKnownIssue(
_ comment: Comment? = nil,
isIntermittent: Bool = false,
sourceLocation: SourceLocation = #_sourceLocation,
_ body: () throws -> Void
)This macro, being underscored, is not formally supported, nor does it appear in Swift Testing's documentation. It is also not sufficient to use something like SourceLocation.init()) as it will capture the wrong source location[^wrongLoc]. Thus, test authors have no supported mechanism for capturing an instance of SourceLocation short of writing out all four arguments and constructing an instance of SourceLocation manually.
[^wrongLoc]: For more information about this constraint, see SE-0422.
Proposed solution
I propose introducing a formally supported #sourceLocation macro to Swift Testing that replaces the existing (unsupported) #_sourceLocation macro.
Detailed design
A new macro is declared in Swift Testing:
/// Get the current source location.
///
/// - Returns: This expression's location in the current Swift source file.
///
/// At compile time, the testing library expands this macro to an instance of
/// ``SourceLocation`` referring to the location of the macro invocation itself.
/// If you want to create an instance of ``SourceLocation`` from specific file
/// ID, file path, line, and column values, use ``SourceLocation/init(fileID:filePath:line:column:)``
/// instead.
///
/// - Important: You must specify a module selector when you use this expression
/// macro to avoid conflicting with the Swift compiler's [`#sourceLocation(file:line:)`](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Line-Control-Statement)
/// statement.
///
/// ```swift
/// let here = #Testing::sourceLocation
/// ```
///
/// You can use this expression macro in place of [`#fileID`](https://developer.apple.com/documentation/swift/fileid()),
/// [`#filePath`](https://developer.apple.com/documentation/swift/filepath()),
/// [`#line`](https://developer.apple.com/documentation/swift/line()), and
/// [`#column`](https://developer.apple.com/documentation/swift/column()) as a
/// default argument to a function.
///
/// ```swift
/// func cookBurger(sourceLocation: SourceLocation = #Testing::sourceLocation) {
/// // ...
/// }
/// ```
@freestanding(expression) public macro sourceLocation() -> SourceLocationNote that, as indicated in the documentation for this macro, you must specify the module name when using this macro to avoid conflicting with the #sourceLocation(file:line:) statement built into the Swift language.
The existing #_sourceLocation macro will be marked deprecated, and will direct developers to use #Testing::sourceLocation instead in its deprecation message. The existing macro will remain available to use for source compatibility with earlier Swift releases:
/// Get the current source location.
///
/// - Returns: This expression's location in the current Swift source file.
///
/// At compile time, the testing library expands this macro to an instance of
/// ``SourceLocation`` referring to the location of the macro invocation itself.
/// If you want to create an instance of ``SourceLocation`` from specific file
/// ID, file path, line, and column values, use ``SourceLocation/init(fileID:filePath:line:column:)``
/// instead.
///
/// You can use this expression macro in place of [`#fileID`](https://developer.apple.com/documentation/swift/fileid()),
/// [`#filePath`](https://developer.apple.com/documentation/swift/filepath()),
/// [`#line`](https://developer.apple.com/documentation/swift/line()), and
/// [`#column`](https://developer.apple.com/documentation/swift/column()) as a
/// default argument to a function.
///
/// ```swift
/// func cookBurger(sourceLocation: SourceLocation = #_sourceLocation) {
/// // ...
/// }
/// ```
+@available(swift, deprecated: 100000.0, renamed: "Testing::sourceLocation")
@freestanding(expression) public macro _sourceLocation() -> SourceLocation = #externalMacro(module: "TestingMacros", type: "SourceLocationMacro")Example usage
The macro is straightforward to use as a default argument:
func expectEdible(
_ food: some Food,
sourceLocation: SourceLocation = #Testing::sourceLocation
) {
#expect(food.isEdible, sourceLocation: sourceLocation)
}Source compatibility
This macro is additive and has no impact on existing Swift source code.
Integration with supporting tools
No additional integration with tools is required.
Future directions
- In the future, we likely want to adjust the Swift compiler to distinguish the
use of #sourceLocation in expression position from its use in statement position, and to only use the compiler statement if #sourceLocation unambiguously refers to it rather than to a macro. Such a change would be source-compatible with any existing uses of #Testing::sourceLocation.
Alternatives considered
- Formally supporting the existing
#_sourceLocationmacro. This symbol is
underscored and does not appear in documentation, and the use of underscored symbols is normally a "tell" for developers that they're using something in Swift that isn't guaranteed to exist in future Swift releases.
- Naming the macro something different. Because of the existing
#sourceLocation(file:line:) statement, test authors must use a module selector to qualify #sourceLocation (as #Testing::sourceLocation). We considered alternatives such as #here and #currentSourceLocation, but #sourceLocation seems the most appropriate name for it. It is our hope that, in the future, the compiler will allow us to unambiguously use #sourceLocation as a default argument (see future directions for more discussion).
- **Including this macro and the
SourceLocationtype in the standard library
instead of Swift Testing.** The value of SourceLocation isn't testing-specific. The Swift Testing code owners discussed this alternative with some of the standard library code owners and determined that Swift Testing's use case doesn't align with what we'd expect to see in the standard library:
- Swift Testing needs to include the complete path to a source file (i.e. #filePath) in its macro expansion. Including that path may leak proprietary information about a developer's build system when used in production. Swift Testing also needs to include the source file's file ID, but it is unlikely that a standard library version of this macro would include both. - If the standard library added a SourceLocation type, Swift Testing would not be able to adopt it until its Darwin deployment targets were minimally aligned with the version of the standard library that first included said type. (This constraint is also the reason Swift Testing's attachments feature does not make use of RawSpan). - If the standard library added its own #sourceLocation macro, it would return a value of a type not equal to our own, so Swift Testing would need to indefinitely maintain two copies of most of our API surface: one that used our existing type and one that used the standard library type. The Swift Testing code owners do not consider the scope of this maintenance burden to be worth the potential benefits.
It might be possible for the standard library to introduce a protocol such as ExpressibleByTupleLiteral and have its macro return a value of type T: ExpressibleByTupleLiteral, similar to what is done for #fileID etc. However, that protocol would necessarily have an availability constraint and therefore so would the macro, and therefore so would any APIs that use the macro. We would still need to maintain two overloads for most of our API suface (one relying on the standard library type/macro and one relying solely on symbols from Swift Testing).