swiftlang / swift

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

Bad diagnostics with multiple typed throws #74554

Open NachoSoto opened 1 week ago

NachoSoto commented 1 week ago

Description

Multiple throws of the same type inside of a function don't compile (even though I'd expect them to), and instead produce FOUR bad diagnostics.

Reproduction

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

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

func h() {
  do {
    // Errors thrown from here are not handled because the enclosing catch is not exhaustive
    try f()

    do {
      try g()
    } catch {
      // Error is not handled because the enclosing catch is not exhaustive
      throw Error.b
    }
  } catch .a { // Type '_ErrorCodeProtocol' has no member 'a'

  } catch .b { // Type '_ErrorCodeProtocol' has no member 'b'

  }
}

Expected behavior

Code compiles since all errors are handled.

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

This is related to #74555, but this also doesn't compile:

func h() {
  do {
    // Errors thrown from here are not handled because the enclosing catch is not exhaustive
    try f()

    do {
      try g()
    } catch {
      // Error is not handled because the enclosing catch is not exhaustive
      throw Error.b
    }
  } catch _ as Error {
  }
}
Jumhyn commented 1 week ago

This looks to me like an issue with properly considering the explicit throw considering that in the following I'm seeing i fail to compile but j succeed without issue:

func i() {
  do {
    throw MyError.b
  } catch _ as MyError {
  }
}

func j() {
    do {
        try f()
        do {
            try g()
        } catch {
            try f()
        }
    } catch _ as MyError {
    }
}

Never mind, this is expected

Jumhyn commented 1 week ago

Ah, also note this part of the typed throws proposal:

Note that the only way to write an exhaustive do...catch statement is to have an unconditional catch block. The dynamic checking provided by is or as patterns in the catch block cannot be used to make a catch exhaustive, even if the type specified is the same as the type thrown from the body of the do:

func f() {
  do /*infers throws(CatError)*/ {
    try callCat()
  } catch let ce as CatError {

  } // error: do...catch is not exhaustive, so this code rethrows CatError and is ill-formed
}

(though that does make it interesting that j compiles above, especially with a warning that the as cast is redundant.)

Jumhyn commented 1 week ago

This is expected without the experimental feature FullTypedThrows enabled. The implementation has not been completed and this feature will not be part of the Swift 6 language mode.