For the type inference issue I would extend solution 2 to the following:
throws
functions should never be inferred to be the typed throw but the base throws
, unless there is an explicitly specified typed throws. It would behave like follows:
struct SomeError: Error {}
func foo1(_ bar: () throws -> Int) {}
foo1 { throw SomeError() }
// bar has type `() throws -> Int`,
// there is no explicit type after `throws` in the declaration
foo1 { 5 }
// bar has type `() throws -> Int`,
// but we emit a warning, that no error is thrown
func foo2<T>(_ bar: T) {}
foo2 { throw SomeError() }
// bar has type `() throws -> ()`,
// we don't infer `SomeError` as the thrown type here,
// because again there is no explicit type after `throws`
// in the declaration (we consider not having an explicit
// `throws` or an explicit function type at all the same)
func foo3<E>(_ bar: () throws E -> Int) {}
foo3 { throw SomeError() }
// bar has type `() throws SomeType -> Int`,
// because there is an explicit typed throws
foo3 { 5 }
// bar has type `() throws Never -> Int` or `() -> Int` for short
Variables:
let value = { throw SomeError() }
// value has type `() throws -> ()`,
// again there was no type explicitly specified typed throws
// if we want a variable to have a typed throws, we declare it like so
let value1: () throws SomeError -> () = { throw SomeError() }
// the same applies to other types, like arrays, dictionaries, tuples, etc.
let array1 = [{ throws SomeError() }]
// array1 has type `[() throws -> ()]`
let dict1 = ["foo": { throws SomeError() }]
// dict1 has type `[String: () throws -> ()]`
let tuple1 = (5, { throws SomeError() })
// tuple1 has type `(Int, () throws -> ())`
// to make them have typed throws:
let array2: [() throws SomeError -> ()] = [{ throws SomeError() }]
let dict2: [String, () throws SomeError -> ()] = ["foo": { throws SomeError() }]
let tuple2: (Int, () throws SomeError -> ()) = (5, { throws SomeError() })
Other scenarios behave predictably:
struct Foo<T> {
let t: T
}
let value2 = Foo { throw SomeError() }
// value2 has type `Foo<() throws -> ()>`,
// because `T` has no explicit typed throws
let value3: Foo<() throws -> ()> = Foo { throw SomeError() }
// or
let value4 = Foo<() throws -> ()>(t: { throw SomeError() })
struct Bar<E> {
let bar: () throws E -> ()
}
let value5 = Bar { throw SomeError() }
// value5 has type `Foo<() throws SomeError -> ()>`,
// because bar has a function type with explicitly specified typed throws
This would be my solution to this problem. I think, it's a good trade-off between keeping source-stability and having expressivity.