Repository: Quick/Nimble (swift)

Issue:
Can't combine async expectations with sync expectations in one test.
As soon as you make one async call in your tests, you can no longer use standard `expect(x).to(...)`.

The following will refuse to compile:

```swift
class MySpec: QuickSpec {
    override func spec() {
        it("") {
            await expect { await someAsyncFunction() }.to(...)
            expect(1).to(equal(1))
        }
    }
}
```

And in fact, you don't even have to try to combine async expectations with sync expectations. For example, the following will refuse to compile:

```swift
class MySpec: QuickSpec {
    override func spec() {
        it("") {
            await someAsyncFunction()
            expect(1).to(equal(1))
        }
    }
}
```

This is, I think, the swift compiler becoming confused by the overloaded use of `expect`. When I made AsyncExpectation, I duplicated all the forms of `expect`, which essentially results in the following:

- `func expect<T>(_ expression: @autoclosure @escaping () throws -> T?) -> SyncExpectation<T>`
- `func expect<T>(_ expression: @autoclosure @escaping () async throws -> T?) async -> AsyncExpectation<T>`

And I think swift is becoming confused by this overloading.

Which results in this incredibly disappointing user experience.

Pull request:
Resolves https://github.com/Quick/Nimble/issues/1019

The autoclosure with async expect was causing the swift compiler to assume all expect calls where async expects if you used any kind of async functionality in your test

Breaking change, but I'm going to release it as a minor update version update (11.2.0)

Patch:
diff --git a/Sources/Nimble/DSL+AsyncAwait.swift b/Sources/Nimble/DSL+AsyncAwait.swift
index 964ce181b..ae575f593 100644
--- a/Sources/Nimble/DSL+AsyncAwait.swift
+++ b/Sources/Nimble/DSL+AsyncAwait.swift
@@ -11,7 +11,7 @@ private func convertAsyncExpression<T>(_ asyncExpression: () async throws -> T)
 }
 
 /// Make an ``AsyncExpectation`` on a given actual value. The value given is lazily evaluated.
-public func expect<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @escaping () async throws -> T?) async -> AsyncExpectation<T> {
+public func expect<T>(file: FileString = #file, line: UInt = #line, _ expression: @escaping () async throws -> T?) async -> AsyncExpectation<T> {
     return AsyncExpectation(
         expression: Expression(
             expression: await convertAsyncExpression(expression),
@@ -20,7 +20,7 @@ public func expect<T>(file: FileString = #file, line: UInt = #line, _ expression
 }
 
 /// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked.
-public func expect<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() async throws -> T)) async -> AsyncExpectation<T> {
+public func expect<T>(file: FileString = #file, line: UInt = #line, _ expression: () -> (() async throws -> T)) async -> AsyncExpectation<T> {
     return AsyncExpectation(
         expression: Expression(
             expression: await convertAsyncExpression(expression()),
@@ -29,7 +29,7 @@ public func expect<T>(file: FileString = #file, line: UInt = #line, _ expression
 }
 
 /// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked.
-public func expect<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() async throws -> T?)) async -> AsyncExpectation<T> {
+public func expect<T>(file: FileString = #file, line: UInt = #line, _ expression: () -> (() async throws -> T?)) async -> AsyncExpectation<T> {
     return AsyncExpectation(
         expression: Expression(
             expression: await convertAsyncExpression(expression()),
@@ -38,7 +38,7 @@ public func expect<T>(file: FileString = #file, line: UInt = #line, _ expression
 }
 
 /// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked.
-public func expect(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() async throws -> Void)) async -> AsyncExpectation<Void> {
+public func expect(file: FileString = #file, line: UInt = #line, _ expression: () -> (() async throws -> Void)) async -> AsyncExpectation<Void> {
     return AsyncExpectation(
         expression: Expression(
             expression: await convertAsyncExpression(expression()),


Test patch:
diff --git a/Tests/NimbleTests/DSLTest.swift b/Tests/NimbleTests/DSLTest.swift
index 0d4203133..369bc294c 100644
--- a/Tests/NimbleTests/DSLTest.swift
+++ b/Tests/NimbleTests/DSLTest.swift
@@ -45,14 +45,6 @@ final class DSLTest: XCTestCase {
         let _: SyncExpectation<Void> = expect { () -> Void in return () }
     }
 
-    func testExpectAsyncAutoclosureNonThrowing() async throws {
-        let _: AsyncExpectation<Int> = await expect(await nonThrowingAsyncInt())
-    }
-
-    func testExpectAsyncAutoclosureThrowing() async throws {
-        let _: AsyncExpectation<Int> = await expect(try await throwingAsyncInt())
-    }
-
     func testExpectAsyncClosure() async throws {
         let _: AsyncExpectation<Int> = await expect { 1 }
         let _: AsyncExpectation<Int> = await expect { await nonThrowingAsyncInt() }
@@ -70,4 +62,11 @@ final class DSLTest: XCTestCase {
         let _: AsyncExpectation<Void> = await expect { return () }
         let _: AsyncExpectation<Void> = await expect { () -> Void in return () }
     }
+
+    func testExpectCombinedSyncAndAsyncExpects() async throws {
+        await expect { await nonThrowingAsyncInt() }.to(equal(1))
+        expect(1).to(equal(1))
+
+        expect { nonThrowingInt() }.to(equal(1))
+    }
 }