Skip to content

Commit 29ab3c8

Browse files
committed
[ST-NNNN] Macro for getting the current source location
1 parent f311762 commit 29ab3c8

1 file changed

Lines changed: 174 additions & 0 deletions

File tree

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# Macro for getting the current source location
2+
3+
* Proposal: [ST-NNNN](NNNN-sourcelocation-macro.md)
4+
* Authors: [Jonathan Grynspan](https://github.com/grynspan)
5+
* Review Manager: TBD
6+
* Status: **Awaiting review**
7+
* Bug: rdar://178259171
8+
* Implementation: [swiftlang/swift-testing#1733](https://github.com/swiftlang/swift-testing/pull/1733)
9+
* Review: ([pitch](https://forums.swift.org/...))
10+
11+
## Introduction
12+
13+
Swift Testing includes a type, [`SourceLocation`](https://developer.apple.com/documentation/testing/sourcelocation),
14+
that represents the precise location of something in a file (typically a .swift
15+
file). Various Swift Testing API takes an instance of this type in order to
16+
correctly attribute diagnostics and test issues that occur at test time. This
17+
proposal covers introducing a macro that can be used as a default argument to
18+
such functions.
19+
20+
## Motivation
21+
22+
The Swift standard library includes macros to get the current file ID, file
23+
path, line, and column at compile time. These macros can then be used as default
24+
function arguments to allow automagical capture of the caller's location in
25+
source. For example, [`fatalError()`](https://developer.apple.com/documentation/swift/fatalerror(_:file:line:))
26+
takes the file and line number and prints them to `stderr` when called.
27+
28+
Swift Testing needs to capture all four of these values, which is quite verbose
29+
and somewhat tedious to work with, so various Swift Testing APIs encapsulate all
30+
of them in a single argument of type `SourceLocation`. Swift Testing provides a
31+
`#_sourceLocation` macro that expands, at compile time, to an appropriate
32+
expression:
33+
34+
```swift
35+
public func withKnownIssue(
36+
_ comment: Comment? = nil,
37+
isIntermittent: Bool = false,
38+
sourceLocation: SourceLocation = #_sourceLocation,
39+
_ body: () throws -> Void
40+
)
41+
```
42+
43+
This macro, being underscored, is not formally supported, nor does it appear in
44+
Swift Testing's documentation. It is also not sufficient to use something like
45+
[`SourceLocation.init()`](https://developer.apple.com/documentation/testing/sourcelocation/init(fileid:filepath:line:column:))
46+
as it will capture the _wrong_ source location. Thus, test authors have no
47+
supported mechanism for capturing an instance of `SourceLocation` short of
48+
writing out all four arguments and constructing an instance of `SourceLocation`
49+
manually.
50+
51+
## Proposed solution
52+
53+
I propose introducing a formally supported `#sourceLocation` macro to Swift
54+
Testing that replaces the existing (unsupported) `#_sourceLocation` macro.
55+
56+
## Detailed design
57+
58+
A new macro is declared in Swift Testing:
59+
60+
```swift
61+
/// Get the current source location.
62+
///
63+
/// - Returns: This expression's location in the current Swift source file.
64+
///
65+
/// At compile time, the testing library expands this macro to an instance of
66+
/// ``SourceLocation`` referring to the location of the macro invocation itself.
67+
/// If you want to create an instance of ``SourceLocation`` from specific file
68+
/// ID, file path, line, and column values, use ``SourceLocation/init(fileID:filePath:line:column:)``
69+
/// instead.
70+
///
71+
/// - Important: You must specify a module selector when you use this expression
72+
/// 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)
73+
/// statement.
74+
///
75+
/// ```swift
76+
/// let here = #Testing::sourceLocation
77+
/// ```
78+
///
79+
/// You can use this expression macro in place of [`#fileID`](https://developer.apple.com/documentation/swift/fileid()),
80+
/// [`#filePath`](https://developer.apple.com/documentation/swift/filepath()),
81+
/// [`#line`](https://developer.apple.com/documentation/swift/line()), and
82+
/// [`#column`](https://developer.apple.com/documentation/swift/column()) as a
83+
/// default argument to a function.
84+
///
85+
/// ```swift
86+
/// func cookBurger(sourceLocation: SourceLocation = #Testing::sourceLocation) {
87+
/// // ...
88+
/// }
89+
/// ```
90+
@freestanding(expression) public macro sourceLocation() -> SourceLocation
91+
```
92+
93+
Note that, as indicated in the documentation for this macro, you must specify
94+
the module name when using this macro to avoid conflicting with the [`#sourceLocation(file:line:)`](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Line-Control-Statement)
95+
statement built into the Swift language.
96+
97+
The existing `#_sourceLocation` macro will be marked deprecated and renamed, but
98+
will remain available to use for source compatibility with earlier Swift
99+
releases:
100+
101+
```diff
102+
/// Get the current source location.
103+
///
104+
/// - Returns: This expression's location in the current Swift source file.
105+
///
106+
/// At compile time, the testing library expands this macro to an instance of
107+
/// ``SourceLocation`` referring to the location of the macro invocation itself.
108+
/// If you want to create an instance of ``SourceLocation`` from specific file
109+
/// ID, file path, line, and column values, use ``SourceLocation/init(fileID:filePath:line:column:)``
110+
/// instead.
111+
///
112+
/// You can use this expression macro in place of [`#fileID`](https://developer.apple.com/documentation/swift/fileid()),
113+
/// [`#filePath`](https://developer.apple.com/documentation/swift/filepath()),
114+
/// [`#line`](https://developer.apple.com/documentation/swift/line()), and
115+
/// [`#column`](https://developer.apple.com/documentation/swift/column()) as a
116+
/// default argument to a function.
117+
///
118+
/// ```swift
119+
/// func cookBurger(sourceLocation: SourceLocation = #_sourceLocation) {
120+
/// // ...
121+
/// }
122+
/// ```
123+
+@available(swift, deprecated: 100000.0, renamed: "Testing::sourceLocation")
124+
@freestanding(expression) public macro _sourceLocation() -> SourceLocation = #externalMacro(module: "TestingMacros", type: "SourceLocationMacro")
125+
126+
127+
### Example usage
128+
129+
The macro is straightforward to use as a default argument:
130+
131+
```swift
132+
func expectEdible(
133+
_ food: some Food,
134+
sourceLocation: SourceLocation = #Testing::sourceLocation
135+
) {
136+
#expect(food.isEdible, sourceLocation: sourceLocation)
137+
}
138+
```
139+
140+
## Source compatibility
141+
142+
This macro is additive and has no impact on existing Swift source code.
143+
144+
## Integration with supporting tools
145+
146+
No additional integration with tools is required.
147+
148+
## Future directions
149+
150+
- In the future, we likely want to adjust the Swift compiler to distinguish the
151+
use of `#sourceLocation` in expression position from its use in statement
152+
position, and to only use the compiler statement if `#sourceLocation`
153+
unambiguously refers to it rather than to a macro.
154+
155+
## Alternatives considered
156+
157+
- **Formally supporting the existing `#_sourceLocation` macro.** This symbol is
158+
underscored and does not appear in documentation, and the use of underscored
159+
symbols is normally a "tell" for developers that they're using something in
160+
Swift that isn't guaranteed to exist in future Swift releases.
161+
162+
- **Naming the macro something different.** We considered alternatives such as
163+
`#here` and `#currentSourceLocation`, but `#sourceLocation` seems the most
164+
appropriate name for it.
165+
166+
- **Including this macro and the `SourceLocation` type in the standard library
167+
instead of Swift Testing.** The value of `SourceLocation` isn't
168+
testing-specific. However, it necessarily includes the complete path to a
169+
source file (i.e. `#filePath`) that may leak proprietary information about a
170+
developer's build system when used in production. As such, it is not suitable
171+
for general use in the Swift ecosystem. It may be appropriate for the standard
172+
library to include some _equivalent_ macro that can optimize away individual
173+
members the calling code doesn't use, but such a macro is beyond the scope of
174+
this proposal.

0 commit comments

Comments
 (0)