The maintainers of large Swift libraries found that, in practice, this duplication was very painful. For instance, consider what Corelibs XCTest (which fortunately doesn’t need to maintain source compatibility quite so stringently) might have to do for just one #filePath
-capturing assertion:
#if compiler(>=5.3)
/// This function emits a test failure if the general `Boolean` expression passed
/// to it evaluates to `false`.
///
/// - Requires: This and all other XCTAssert* functions must be called from
/// within a test method, as passed to `XCTMain`.
/// Assertion failures that occur outside of a test method will *not* be
/// reported as failures.
///
/// - Parameter expression: A boolean test. If it evaluates to `false`, the
/// assertion fails and emits a test failure.
/// - Parameter message: An optional message to use in the failure if the
/// assertion fails. If no message is supplied a default message is used.
/// - Parameter file: The file name to use in the error message if the assertion
/// fails. Default is the file containing the call to this function. It is
/// rare to provide this parameter when calling this function.
/// - Parameter line: The line number to use in the error message if the
/// assertion fails. Default is the line number of the call to this function
/// in the calling file. It is rare to provide this parameter when calling
/// this function.
///
/// - Note: It is rare to provide the `file` and `line` parameters when calling
/// this function, although you may consider doing so when creating your own
/// assertion functions. For example, consider the following custom assertion:
///
/// ```
/// // AssertEmpty.swift
///
/// func AssertEmpty<T>(_ elements: [T]) {
/// XCTAssertEqual(elements.count, 0, "Array is not empty")
/// }
/// ```
///
/// Calling this assertion will cause XCTest to report the failure occurred
/// in the file where `AssertEmpty()` is defined, and on the line where
/// `XCTAssertEqual` is called from within that function:
///
/// ```
/// // MyFile.swift
///
/// AssertEmpty([1, 2, 3]) // Emits "AssertEmpty.swift:3: error: ..."
/// ```
///
/// To have XCTest properly report the file and line where the assertion
/// failed, you may specify the file and line yourself:
///
/// ```
/// // AssertEmpty.swift
///
/// func AssertEmpty<T>(_ elements: [T], file: StaticString = #file, line: UInt = #line) {
/// XCTAssertEqual(elements.count, 0, "Array is not empty", file: file, line: line)
/// }
/// ```
///
/// Now calling failures in `AssertEmpty` will be reported in the file and on
/// the line that the assert function is *called*, not where it is defined.
public func XCTAssert(_ expression: @autoclosure () throws -> Bool, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) {
XCTAssertTrue(try expression(), message(), file: file, line: line)
}
#else
/// This function emits a test failure if the general `Boolean` expression passed
/// to it evaluates to `false`.
///
/// - Requires: This and all other XCTAssert* functions must be called from
/// within a test method, as passed to `XCTMain`.
/// Assertion failures that occur outside of a test method will *not* be
/// reported as failures.
///
/// - Parameter expression: A boolean test. If it evaluates to `false`, the
/// assertion fails and emits a test failure.
/// - Parameter message: An optional message to use in the failure if the
/// assertion fails. If no message is supplied a default message is used.
/// - Parameter file: The file name to use in the error message if the assertion
/// fails. Default is the file containing the call to this function. It is
/// rare to provide this parameter when calling this function.
/// - Parameter line: The line number to use in the error message if the
/// assertion fails. Default is the line number of the call to this function
/// in the calling file. It is rare to provide this parameter when calling
/// this function.
///
/// - Note: It is rare to provide the `file` and `line` parameters when calling
/// this function, although you may consider doing so when creating your own
/// assertion functions. For example, consider the following custom assertion:
///
/// ```
/// // AssertEmpty.swift
///
/// func AssertEmpty<T>(_ elements: [T]) {
/// XCTAssertEqual(elements.count, 0, "Array is not empty")
/// }
/// ```
///
/// Calling this assertion will cause XCTest to report the failure occurred
/// in the file where `AssertEmpty()` is defined, and on the line where
/// `XCTAssertEqual` is called from within that function:
///
/// ```
/// // MyFile.swift
///
/// AssertEmpty([1, 2, 3]) // Emits "AssertEmpty.swift:3: error: ..."
/// ```
///
/// To have XCTest properly report the file and line where the assertion
/// failed, you may specify the file and line yourself:
///
/// ```
/// // AssertEmpty.swift
///
/// func AssertEmpty<T>(_ elements: [T], file: StaticString = #file, line: UInt = #line) {
/// XCTAssertEqual(elements.count, 0, "Array is not empty", file: file, line: line)
/// }
/// ```
///
/// Now calling failures in `AssertEmpty` will be reported in the file and on
/// the line that the assert function is *called*, not where it is defined.
public func XCTAssert(_ expression: @autoclosure () throws -> Bool, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
XCTAssertTrue(try expression(), message(), file: file, line: line)
}
#endif
(Yes, you have to repeat the entire doc comment.)
That’s...rather unfortunate. Library maintainers basically said that, if they had realized they’d need to duplicate so much code to adopt #filePath
without breaking backwards compatibility, they would have asked for the proposal to be revised.