Hi all,
In Swift 3.0, the following examples both typecheck:
let fn1: ([Int]) -> () = {
let _: [Int] = $0
}
let fn2: (Int...) -> () = {
let _: [Int] = $0
}
This stopped working due to a regression in master so I'm looking at fixing it.
While investigating this, I noticed that this variant with two parameters
doesn’t work in either release:
let fn3: (Int, Int...) -> () = { // cannot convert value of type '(_, _) -> ()'
to specified type '(Int, Int...) -> ()'
let _: Int = $0
let _: [Int] = $1
}
The diagnostic doesn’t make sense, which suggests there’s a deeper underlying
problem.
Indeed, the reason the ‘fn2’ example works in Swift 3.0 is because we bind $0
to the single-element tuple type (Int…), which admits an implicit conversion to
[Int]. The closure literal gets the type ((Int…)) -> () — note the extra pair
of parentheses here. This works mostly on accident. For example if we bind $0
to a generic parameter, SILGen blows up:
func id<T>(t: T) -> T {
return t
}
let fn4: (Int...) -> () = {
id(t: $0) // segfault here
}
I think it would be better if we permitted an implicit conversion between (T…)
-> () and ([T]) -> (), or more precisely, erase varargs when matching function
arguments in matchFunctionTypes() if we’re performing a Subtype conversion or
higher.
After adding this to CSSimplify, I notice that in fn2, $0 now gets type [Int]
and the closure has type ([Int]) -> (), wrapped in a FunctionConversionExpr
converting to (Int…) -> (), which ends up being a no-op since varargs are
erased in SILGen. Also, fn3 and fn4 start working; in fn3, $1 gets type [Int],
and in fn4, we also correctly bind the generic parameter to [Int]. I think this
is a better situation overall. Values should not have types containing vararg
tuples, and we should prevent these types from showing up in the type system as
much as possible.
However, this more general conversion rule also means the following is allowed,
whereas it did not typecheck before:
func varargToArray<T>(fn: @escaping (T...) -> ()) -> ([T]) -> () {
return fn
}
func arrayToVararg<T>(fn: @escaping ([T]) -> ()) -> (T...) -> () {
return fn
}
This is essentially what Dollar.swift was doing, but they were using a closure
literal to achieve it.
At this time, the conversion can be performed without thunking, but if varargs
ever get a different representation, we can still thunk the conversion like we
do for re-abstraction, optionality changes, existential erasure in function
types, etc.
Does anyone foresee any problems with this approach? We could also conceivably
limit this conversion to closure literals only, and not general subtype
conversions.
Slava
_______________________________________________
swift-dev mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-dev