swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.61k stars 10.37k forks source link

Type inference breaking with generic typed throws and autoclosures #75430

Open ZevEisenberg opened 3 months ago

ZevEisenberg commented 3 months ago

Description

In a generic function that takes a typed-throwing function and an error of the same type as parameters, type inference is failing when trying to call the function and pass in an instance of the error. But the fully-qualified type compiles.

Reproduction

Put the following in a main.swift and try to run it with swift main.swift. Observe a compile error on the last line of code.

// Simplified version of an XCTest utility I'm trying to write.
func assertThrowsGeneric<Failure: Error & Equatable>(
  _ expression: @autoclosure () throws(Failure) -> some Any,
  _ expectedError: @autoclosure () -> Failure
) {
  do {
    _ = try expression()
  } catch {
    assert(error == expectedError())
  }
}

enum AuthError: Error, Equatable {
  case noAccount(email: String)
}

func myLoginFunction(
  email: String
) throws(AuthError) {
  guard email == "theOnlyAccountThatMatters@me.com" else {
    throw .noAccount(email: email)
  }

  // If we made it here, we're logged in!
}

do {
  try myLoginFunction(email: "foo")
} catch {
  switch error {
    // ✅ Type inference is working here!
  case .noAccount(email: let email):
    print("no account \(email)")
  }
}

// ✅ Fully qualified type: compiles!
assertThrowsGeneric(try myLoginFunction(email: "nope"), AuthError.noAccount(email: "nope"))

// ❌ Trying to use type inference: fails!
assertThrowsGeneric(try myLoginFunction(email: "nope"), .noAccount(email: "nope"))
//                                                       ^ 🛑 Type 'Equatable' has no member 'noAccount'

Expected behavior

Code compiles.

Environment

swift-driver version: 1.112.3 Apple Swift version 6.0 (swiftlang-6.0.0.6.8 clang-1600.0.23.1) Target: arm64-apple-macosx14.0

Also tried on Swift 6.0 Development Snapshot 2024-07-21 (a) and Swift Development Snapshot 2024-07-22 (a) with the same result.

Additional information

No response

gregomni commented 3 months ago

It looks like the issue might be that the throw type isn't making it out of the closure.

With:

func assertThrowsGeneric<Failure: Error & Equatable>(_: () throws(Failure) -> (), _: Failure) {}

enum AuthError: Error, Equatable {
  case noAccount
}

func myLoginFunction() throws(AuthError) {}

This compiles (explicit function type):

assertThrowsGeneric(myLoginFunction, .noAccount)

This does not (explicit closure -- autoclosure also fails):

assertThrowsGeneric({try myLoginFunction()}, .noAccount) // error: type 'any Error' has no member 'noAccount'
assertThrowsGeneric({try myLoginFunction()}, AuthError.noAccount) // error: invalid conversion of thrown error type 'any Error' to 'AuthError'
gregomni commented 3 months ago

The way that I read this: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0413-typed-throws.md#closure-thrown-type-inference, the current behavior MIGHT be arguably correct in Swift 5 because "throw statements within the closure body are treated as having the type any Error in Swift 5". This isn't a spelled out throw statement, but maybe the lack of propagation of the throw type to the closures type is expected.

However, I currently see the same diagnoses and failures when specifying -swift-version 6 to the compiler, which there seems no question is a bug.

JessyCatterwaul commented 3 months ago

I duplicated this as #75674. And I submitted it after having read and tried this! 😣 So this issue should be simplified to demonstrate the problem, or closed.

@gregomni Your reduction is useful. But this following statement is not true; you just need to use the currently-necessary explicit syntax to see it.

This does not (explicit closure -- autoclosure also fails):

The syntax to make your code compile is:

assertThrowsGeneric({ () throws(AuthError) in try myLoginFunction() }, .noAccount)

Fixing that is important, but a different issue. The relevant problem is only about autoclosures.

ZevEisenberg commented 3 months ago

I added autoclosure to the title of this one. I leave it up to the powers that be to decide which one to close.