SE-315 was accepted with a notable restriction on the positions where placeholder types could be used. To quote the text:
An earlier draft of this proposal allowed for the use of placeholders as top-level types, so that one could write
let x: _ = 0.0 // type of x is inferred as Double
Compared to other uses of this feature, top-level placeholders are clearly of more limited utility. In type annotations (as above), they merely serve as a slightly more explicit way to indicate "this type is inferred," and they are similarly unhelpful in
as
casts. There is some use for top-level placeholders in type expression position, particularly when passing a metatype value as a parameter. For instance, Combine'ssetFailureType(to:)
operator could be used with a top-level placeholder to make conversions between failure types more lightweight when necessary:
After having hacked on some better diagnostics for placeholders in invalid positions, I believe that this restriction is unnecessary and should be relaxed. Placeholders in expression position, regardless of what context they are found in, communicate the user's intent to interact directly with the type inference machinery to fill in some part of their program. In static program text, I agree it can be of diminished semantic importance to any readers, but while editing Swift code the ability to express the intent that you wish for the type checker to give you, the author, a helping hand is invaluable.
Further, to take the example given in the proposal:
However, as Xiaodi Wu points out, allowing placeholders in these positions would have the effect of permitting clients to leave out type names in circumstances where library authors intended the type information to be provided explicitly, such as when using
KeyedDecodingContainer.decode(_:forKey:)
. It is not obviously desirable for users to be able to write, e.g.:self.someProp = try container.decode(_.self, forKey: .someProp)
If the client intends for this to be explicit, then we (as tooling authors) should offer the tools to make that so. The IDE should offer either "soft" expansions (a la option-clicking program elements to view their types) or "hard" expansions like refactoring actions to replace these placeholders with their inferred types. I have already submitted a patch to do just that for most other kinds of placeholder types and have filed rdar://82837396 to have the refactoring actions expanded.
Finally, this restriction is currently implemented inconsistently in the compiler. Here's the current state of affairs:
protocol Publisher {
associatedtype Output
associatedtype Failure
}
struct SetFailureType<Output, Failure>: Publisher {}
extension Publisher {
func setFailureType<T>(to: T.Type) -> SetFailureType<Output, T> {
return .init()
}
}
let _: SetFailureType<Int, String> = Just<Int>().setFailureType(to: _.self) // expected-error {{type placeholder not allowed here}}
let _: SetFailureType<Int, [String]> = Just<Int>().setFailureType(to: [_].self) // But here it's fine?
let _: SetFailureType<Int, (String) -> Double> = Just<Int>().setFailureType(to: ((_) -> _).self) // And here?
let _: SetFailureType<Int, (String, Double)> = Just<Int>().setFailureType(to: (_, _).self) // And here?
To remedy this, I propose we just relax it. The only placeholders that should be diagnosed are those for which insufficient context exists to infer a reasonable type to fill the placeholder in. This brings placeholder types exactly into line with their implementation in the type system as typed holes.