swiftlang / swift-testing

A modern, expressive testing package for Swift
Apache License 2.0
1.81k stars 77 forks source link

Use @Test function parameters to explicitly type array literal argument expressions #808

Open stmontgomery opened 2 weeks ago

stmontgomery commented 2 weeks ago

This solves a problem that users who pass array literals to @Test(arguments: ...) may encounter if the array's elements are heterogeneous. For example:

@Test(arguments: [  // ❌ error: type 'Any' does not conform to the 'Sendable' protocol
  (Int.self, nominalName: "Int"),
  (String.self, nominalName: "String"),
])
func name1(type: Any.Type, nominalName: String) {
  #expect(API.name(type) == nominalName)
}

Due to the use of heterogeneous elements Int.self and String.self, the overall array's type is inferred as [Any]. This leads to two problems:

  1. The @Test(arguments:) macro requires a Collection which is Sendable, and [Any] is not Sendable. This prevents passing the array to the macro at all, since arguments to a macro must typecheck successfully before the macro is expanded.
  2. The test function's parameters are of type Any.Type and String, respectively. For the macro expansion to produce valid code, the array literal needs to have type [(Any.Type, String)].

This PR makes two changes to address those problems:

Continuing the example above, this results in the expanded code behaving as though the array expression had as [(Any.Type, String)] at the end, and the original code now compiles successfully.

Thank you to @ApolloZhu for suggesting the fix to use as ... in the macro expansion!

Concurrency safety

The expanded code from the new @Test overloads is no less concurrency safe than before, because it still calls APIs from the testing library which require Sendable. This means that passing a non-Sendable collection will still result in a compiler diagnostic, just with a different source location than before:

// With the changes in this PR:
struct NonSendable {}

@Test(arguments: [NonSendable()])  // ❌: @__swiftmacro_12TestingTests6unsafe4TestfMp_.swift:14:4: error: type 'NonSendable' does not conform to the 'Sendable' protocol
func unsafe(_ x: NonSendable) {}

Documentation

The new @Test overloads are necessarily public but are hidden from rendered DocC documentation using @_documentation(visibility: private). From an end user's perspective, @Test(arguments:) did, and still does, require Sendable; the only thing changing is where that enforcement occurs. So in terms of documentation, the only overloads we need to document are those that do require Sendable. In fact, for similar reasons, we already hide many of our @Test overloads from documentation so this has precedent.

Checklist:

Fixes swiftlang/swift#76637

stmontgomery commented 2 weeks ago

@swift-ci please test

stmontgomery commented 6 hours ago

I've extracted some of the general macro refactoring into a separate PR (#834)