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
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev

Reply via email to