0

The following code no longer behaves in Swift 4.2 (Xcode 10) the same it was behaving in Swift 4.1 (Xcode 9.4.1):

let key: String! = "key"
let dict: [AnyHashable:Any]? = ["key":"value"]
let val = dict?[key]

In Swift 4.1, val receives the dictionary value ("value"), while in Swift 4.2 it's nil.

The problem goes away if I remove the Implicitly unwrapped optional (IUO), or declare the dictionary as [String:Any], so both

let key: String = "key"
let dict: [AnyHashable:Any]? = ["key":"value"]
let val = dict?[key]

, and

let key: String! = "key"
let dict: [String:Any]? = ["key":"value"]
let val = dict?[key]

result in val ending up holding the string "value".

Is this an intended behaviour in Swift 4.2, or it's a compiler bug?

Asking as I have a huge codebase where both the key and the dictionary come from Objective-C code which is kinda resistant to change. So I was wondering if this change in behaviour is permanent and I should start updating the many places in code that use this pattern, or just wait until a stable build of Xcode 10 is released.

Cristik
  • 30,989
  • 25
  • 91
  • 127
  • 4
    A lot changed with respect to IUOs in the last releases, compare https://swift.org/blog/iuo/ or the Xcode release notes or https://stackoverflow.com/q/39537177/1187415, https://stackoverflow.com/q/39633481/1187415. In most cases, they are handled like regular (strong) optionals. – I wonder *why* you want an IUO as dictionary key. – Martin R Aug 20 '18 at 12:13
  • 3
    I wonder why you are using IUOs at all. They are not deprecated, but their legitimate use is now highly restricted. If `key` is not an instance property it probably should never have been an IUO to begin with. – matt Aug 20 '18 at 12:47
  • @MartinR both the key and the dictionary come from Objective-C, and I don't have too much control over those. – Cristik Aug 20 '18 at 12:49
  • 1
    @Cristik: That might be useful context to add to the question ... – Martin R Aug 20 '18 at 12:50
  • @MartinR thanks for the suggestion, added this this to the question – Cristik Aug 20 '18 at 12:52
  • Even better: Show a (minimal) concrete example of Objective-C code and how it is used from Swift. Perhaps the problem can be fixed by adding the appropriate (non)nullable annotations on the Objective-C side. – Martin R Aug 20 '18 at 12:57
  • @MartinR both Objective-C callees are simple class methods, I have thought about nullability, however there are lots of classes that would need it, and the effort would be huge... – Cristik Aug 20 '18 at 12:59
  • @Cristik The situation sounds parallel to the situation with UIViewController's `view`, which is a `View!` and there's nothing you can do about it. I solve the problem by unwrapping: `let v = self.view!`. Now `v` is an ordinary `View` and we can go about our business happily. I believe that's the solution proposed in the answer you've already received. – matt Aug 20 '18 at 14:28
  • @matt yeah, that's another approach. – Cristik Aug 20 '18 at 14:31
  • "So I was wondering if this change in behaviour is permanent and I should start updating the many places in code that use this pattern, or just wait until a stable build of Xcode 10 is released." If that's the question, my reply would be: this is not a bug, this change is not going away, the proposal has gone through and has been implemented, it's a Good Thing, and you need to adapt or die. :) – matt Aug 20 '18 at 14:33
  • @matt honestly, I'd rather have the lots of Objc code die (or kill it) :) – Cristik Aug 20 '18 at 14:34

1 Answers1

4

There has been a proposal SE-0054 which has been implemented completely in Swift 4.2. In the past, there was a type ImplicitlyUnwrappedOptional which functioned differently from Swift 4.2 (now all the IUOs are of the Optional type in Swift 4.2, not ImplicitlyUnwrappedOptional).

From the proposal (emphasis mine):

If the expression can be explicitly type checked with a strong optional type, it will be. However, the type checker will fall back to forcing the optional if necessary. The effect of this behavior is that the result of any expression that refers to a value declared as T! will either have type T or type T?. For example, in the following code:

let x: Int! = 5
let y = x
let z = x + 0

… x is declared as an IUO, but because the initializer for y type checks correctly as an optional, y will be bound as type Int?. However, the initializer for z does not type check with x declared as an optional (there's no overload of + that takes an optional), so the compiler forces the optional and type checks the initializer as Int.

In your case, the key variable is inferred to be of type String?, so you still have to cast it. This code would work:

let val = dict?[key!]

val has the value Optional("value")

As for why [String:Any] works, according to the emphasised part in the quote, String? can't be used on String , so the compiler will force unwrap it (necessary to let it compile).

Papershine
  • 4,995
  • 2
  • 24
  • 48
  • 2
    As further evidence of this point, if you create your dictionary with `let dict: [AnyHashable:Any]? = ["key":"value", Optional("key"): "value2"]`, you will see that `dict?[key]` selects `value2`, and `dict?[key!]` selects `value`. – vacawama Aug 20 '18 at 12:33
  • Wondering then why the compiler unwraps the IUO automatically if the dictionary is `[String:Any]` – Cristik Aug 20 '18 at 12:53
  • 1
    @Cristik: *"However, the type checker will fall back to forcing the optional if necessary"* – Martin R Aug 20 '18 at 12:58
  • @Cristik in other words, `String?` can't be used on `String`, so the compiler will force unwrap it. – Papershine Aug 20 '18 at 13:00
  • 2
    Thanks! So this is clearly an intended behaviour, and I should start fixing the code right away. – Cristik Aug 20 '18 at 13:01
  • just a suggestion why don't we apple get rid of IUO and just have optionals ( ? ) in place. To me IUO sounds pretty confusing especially in the examples section of ( https://github.com/apple/swift-evolution/blob/master/proposals/0054-abolish-iuo.md ) where the type changes automatically depending on what operation is performed which is dangerous. Also for dev what would be the good practice just to get rid of all the forced bang ! and just use ? optional types ? ( exceptions for IBOutlets ). Please can you help. – Max Oct 05 '18 at 10:18