Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Swift 6 typed-throws unable to detect exhaustive catch statements #74555

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
NachoSoto opened this issue Jun 19, 2024 · 6 comments
Open

Swift 6 typed-throws unable to detect exhaustive catch statements #74555

NachoSoto opened this issue Jun 19, 2024 · 6 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler itself error handling swift 6.2 throws & rethrows Feature → error handling: throws & rethrows type checker Area → compiler: Semantic analysis typed throws Feature → error handling → throws & rethrows: Typed throws unexpected error Bug: Unexpected error

Comments

@NachoSoto
Copy link
Contributor

Description

Consider the example below. Both Error.a and Error.b are caught, but the compiler is unable to infer that.

Reproduction

enum Error: Swift.Error {
  case a
  case b
}

func f() throws(Error) {
  throw .a
}

func g() {
  do {
    // Errors thrown from here are not handled because the enclosing catch is not exhaustive
    try f()
  } catch .a {
  } catch .b {
  }
}

Expected behavior

Code compiles

Environment

swift-driver version: 1.109.2 Apple Swift version 6.0 (swiftlang-6.0.0.3.300 clang-1600.0.20.10)
Target: arm64-apple-macosx14.0

Additional information

No response

@NachoSoto NachoSoto added bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. triage needed This issue needs more specific labels labels Jun 19, 2024
@hborla hborla added typed throws Feature → error handling → throws & rethrows: Typed throws and removed triage needed This issue needs more specific labels labels Jul 14, 2024
@robertmryan
Copy link

robertmryan commented Sep 26, 2024

Just an additional data point. Consider:

enum MyError: Error {
    case one
    case two
}

func funcWithTypedThrow() async throws(MyError) {}

As NachoSoto pointed out, if you use separate catch statements for those individual MyError cases, the compiler will incorrectly conclude that the catches are not exhaustive:

// An example where typed-throw correctly infers the type of the thrown error, but
// incorrectly concludes catch is not exhaustive.

func foo() async {
    do {
        try await funcWithTypedThrow() // Swift 6.0 compiler incorrectly produces error: “Errors thrown from here are not handled because the enclosing catch is not exhaustive”
    } catch .one {
        print("one")
    } catch .two {
        print("two")
    }
}

Interestingly, if we add an extra catch to silence that error, the compiler now realizes that this is unnecessary and will warn that it “will never be executed”:

// In this example, I added extra `catch` to silence the above error, but now 
// the compiler now warns us that that this extra `catch` is unnecessary.

func bar() async {
    do {
        try await funcWithTypedThrow()
    } catch .one {
        print("one")
    } catch .two {
        print("two")
    } catch {  // Swift 6.0 compiler correctly produces warning: “Case will never be executed”
        print("this is required to silence 'non-exhaustive' error, but generates a 'will never be executed' warning")
    }
}

So, it is not the case that it is entirely unaware of the exhaustive catch statements (because it notices if you add the redundant catch), but it just has a false positive error in the absence of the redundant catch.

@NachoSoto
Copy link
Contributor Author

Another example (repro-ing on 6.0.3):

enum E: Error {
  case e1
}

func f() throws(E) {
  throw .e1
}

func g() {
  do {
    // Errors thrown from here are not handled because the enclosing catch is not exhaustive
    try f()
  } catch E.e1 {}
}

@robertmryan
Copy link

FWIW, the issue persists in Swift version 6.1 (swiftlang-6.1.0.109.103 clang-1700.0.13.2), from Xcode 16.3 beta 2, too.


For what it is worth, in the interim, one can use a switch statement, instead:

enum MyError: Error {
    case one
    case two
}

func funcWithTypedThrow() async throws(MyError) {}

func baz() async {
    do {
        try await funcWithTypedThrow()
    } catch {
        switch error {
        case .one: print("one")
        case .two: print("two")
        }
    }
}

Again, it illustrates that it clearly understands that error is a MyError. And this gets around the false positive error message of the simple catch .one/catch .two pattern. This switch is ugly, but it works.

@xwu
Copy link
Collaborator

xwu commented Mar 12, 2025

As @Jumhyn mentioned over in #74554, this behavior is by design, as spelled out in SE-0413 (emphasis added):

Note that the only way to write an exhaustive do...catch statement is to have an unconditional catch block.

@AnthonyLatsis AnthonyLatsis added compiler The Swift compiler itself type checker Area → compiler: Semantic analysis error handling unexpected error Bug: Unexpected error swift 6.2 throws & rethrows Feature → error handling: throws & rethrows labels Mar 24, 2025
@Jon889
Copy link

Jon889 commented May 8, 2025

This just caught me by surprise. Especially as the compiler gives you an error to say you need an unconditional catch block, which if you add it then produces a warning that it's unexecutable. No sane language should tell you do something and then immediately tell you, you don't need it.

This should be basically syntax sugar

func baz() async {
    do {
        try await funcWithTypedThrow()
    } catch .one {
        print("one")
    } catch .two {
        print("two")
    }
}

For this:

func baz() async {
    do {
        try await funcWithTypedThrow()
    } catch {
        switch error {
        case .one: print("one")
        case .two: print("two")
        }
    }
}

@robertmryan
Copy link

robertmryan commented May 8, 2025

@xwu said:

As @Jumhyn mentioned over in #74554, this behavior is by design, as spelled out in SE-0413:

Note that the only way to write an exhaustive do...catch statement is to have an unconditional catch block.

I would not say it is “by design”, but rather more of an acknowledged limitation. As they say a few lines later:

Note: Exhaustiveness checking in the general is expensive at compile time, and the existing language uses the presence of an unconditional catch block as the indicator for an exhaustive do...catch.

So, its not that it was designed with the intent of adding the unnecessary unconditional catch, but rather that it was a compromise introduced to reduce the burden at compile time. But, yes, they know about the problem.

My point is that it’s not reasonable for the compiler to complain that the “Case will never be executed” when it was the compiler that insisted in the unnecessary unconditional catch in the first place. Ideally, the compiler needs to eliminate this unconditional catch requirement, or, worst case scenario, remove the warning about the unconditional catch that it forced us to introduce.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler itself error handling swift 6.2 throws & rethrows Feature → error handling: throws & rethrows type checker Area → compiler: Semantic analysis typed throws Feature → error handling → throws & rethrows: Typed throws unexpected error Bug: Unexpected error
Projects
None yet
Development

No branches or pull requests

6 participants