Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift cannot infer type from context

Tags:

swift

I have a seemingly legitimate function that converts an array to a Dictionary:

func arrayToDictionary<Key : Hashable, Value>
  (source: Array<Value>, key: Value -> Key) -> Dictionary<Key, Value> {
   var dictionary = Dictionary<Key, Value>()
   for element in source {
     let key = key(element)
     dictionary[key] = element
   }
   return dictionary
}

Now, when I try to call it:

let dict = arrayToDictionary([1, 2, 3], { val in return val })

I get an error - Cannot convert the expression's type '($T6, (($T9) -> ($T9) -> $T8) -> (($T9) -> $T8) -> $T8)' to type 'Hashable'

Strangely, if i use implicit return:

let dict = arrayToDictionary([1, 2, 3], { val in val })

or shorthand:

let dict = arrayToDictionary([1, 2, 3], { $0 })

it works just fine. Why?

like image 224
Sergey Aldoukhov Avatar asked Sep 22 '14 17:09

Sergey Aldoukhov


1 Answers

This question can only really be answered by a compiler engineer at Apple, and per the above commenters it could / should be considered a bug, but it is definitely a hole in their shorthand syntax. For questions like these, I have gotten good results from posting to devforums.

The simple rule though is that whenever you have multiple lines / need to use the return keyword, you must either explicitly define the return type, or the type of the value being captured. This limitation might be due to the fact that in the compact / degenerate case, you are guaranteed to only have one point of exit - val in val, where as when you use a return keyword, it is possible to have multiple return points. In this latter case, you might return an Int on one line return 1, and return nil on another. In that case, it would be reasonable to have the compiler complain to make the assumption explicit. In short, this would require more sophisticated type inference in the compiler, and they might not have gotten to that yet.

So TL;DR, I agree with the suggestion to report this as a bug, and in the meantime, specify a return type from the closure. The point remains though that the compiler has enough context to infer the correct type, as you stated.

Note, that in addition to your example, these cases also work:

// inferring var dict as a context for lines below
var dict = arrayToDictionary([1, 2, 3], { val in val })

// dict is already defined, so this works:
dict = arrayToDictionary([1, 2, 3], { val in return val })

// explicit final type, compiler infers backwards
let d2:Dictionary<Int, Int> = arrayToDictionary([1, 2, 3], { val in val })

// explicit return type, compiler infers "forewards"
let d3 = arrayToDictionary([1, 2, 3], { val -> Int in return val })
like image 94
Chris Conover Avatar answered Nov 15 '22 09:11

Chris Conover